flowstate 0.6.2__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. {flowstate-0.6.2 → flowstate-0.7.0}/.github/workflows/publish.yml +1 -1
  2. {flowstate-0.6.2 → flowstate-0.7.0}/.github/workflows/test.yml +1 -1
  3. {flowstate-0.6.2 → flowstate-0.7.0}/PKG-INFO +5 -2
  4. {flowstate-0.6.2 → flowstate-0.7.0}/README.md +1 -1
  5. {flowstate-0.6.2 → flowstate-0.7.0}/pyproject.toml +4 -1
  6. flowstate-0.7.0/src/flow_state/cli/__init__.py +15 -0
  7. flowstate-0.7.0/src/flow_state/cli/app.py +75 -0
  8. flowstate-0.7.0/src/flow_state/cli/callbacks.py +51 -0
  9. flowstate-0.7.0/src/flow_state/cli/cmd_examples.py +63 -0
  10. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/cli/cmd_init.py +9 -1
  11. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/cli/cmd_recover.py +11 -0
  12. flowstate-0.6.2/src/flow_state/cli/cmd_solve.py → flowstate-0.7.0/src/flow_state/cli/cmd_run.py +22 -2
  13. flowstate-0.7.0/src/flow_state/cli/constants.py +11 -0
  14. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/cli/templates.py +6 -1
  15. flowstate-0.6.2/src/flow_state/examples/aedc_t9.toml → flowstate-0.7.0/src/flow_state/examples/aedc_t9/flow_config.toml +1 -1
  16. flowstate-0.6.2/src/flow_state/examples/bam6qt.toml → flowstate-0.7.0/src/flow_state/examples/bam6qt/flow_config.toml +1 -1
  17. flowstate-0.6.2/src/flow_state/examples/sandia.toml → flowstate-0.7.0/src/flow_state/examples/sandia/flow_config.toml +1 -1
  18. flowstate-0.6.2/src/flow_state/examples/stort.toml → flowstate-0.7.0/src/flow_state/examples/stort/flow_config.toml +1 -1
  19. flowstate-0.7.0/src/flow_state/io/examples.py +122 -0
  20. flowstate-0.7.0/src/flow_state/io/flow_config.py +244 -0
  21. flowstate-0.7.0/src/flow_state/io/read_config.py +73 -0
  22. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/PKG-INFO +5 -2
  23. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/SOURCES.txt +7 -5
  24. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/requires.txt +1 -0
  25. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/scm_file_list.json +7 -5
  26. flowstate-0.7.0/src/flowstate.egg-info/scm_version.json +8 -0
  27. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_cli.py +81 -8
  28. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_io.py +89 -0
  29. flowstate-0.6.2/src/flow_state/cli/__init__.py +0 -8
  30. flowstate-0.6.2/src/flow_state/cli/app.py +0 -51
  31. flowstate-0.6.2/src/flow_state/cli/callbacks.py +0 -24
  32. flowstate-0.6.2/src/flow_state/cli/constants.py +0 -4
  33. flowstate-0.6.2/src/flow_state/io/examples.py +0 -95
  34. flowstate-0.6.2/src/flow_state/io/read_config.py +0 -75
  35. flowstate-0.6.2/src/flowstate.egg-info/scm_version.json +0 -8
  36. {flowstate-0.6.2 → flowstate-0.7.0}/.github/workflows/docs.yml +0 -0
  37. {flowstate-0.6.2 → flowstate-0.7.0}/.github/workflows/package.yml +0 -0
  38. {flowstate-0.6.2 → flowstate-0.7.0}/.gitignore +0 -0
  39. {flowstate-0.6.2 → flowstate-0.7.0}/.vscode/extensions.json +0 -0
  40. {flowstate-0.6.2 → flowstate-0.7.0}/.vscode/settings.json +0 -0
  41. {flowstate-0.6.2 → flowstate-0.7.0}/CITATION.cff +0 -0
  42. {flowstate-0.6.2 → flowstate-0.7.0}/LICENSE +0 -0
  43. {flowstate-0.6.2 → flowstate-0.7.0}/apps/webapp/.streamlit/config.toml +0 -0
  44. {flowstate-0.6.2 → flowstate-0.7.0}/apps/webapp/app.py +0 -0
  45. {flowstate-0.6.2 → flowstate-0.7.0}/apps/webapp/requirements.txt +0 -0
  46. {flowstate-0.6.2 → flowstate-0.7.0}/docs/api/architecture.md +0 -0
  47. {flowstate-0.6.2 → flowstate-0.7.0}/docs/api/atmosphere.md +0 -0
  48. {flowstate-0.6.2 → flowstate-0.7.0}/docs/api/core.md +0 -0
  49. {flowstate-0.6.2 → flowstate-0.7.0}/docs/api/index.md +0 -0
  50. {flowstate-0.6.2 → flowstate-0.7.0}/docs/api/solvers.md +0 -0
  51. {flowstate-0.6.2 → flowstate-0.7.0}/docs/api/transport.md +0 -0
  52. {flowstate-0.6.2 → flowstate-0.7.0}/docs/assets/architecture.svg +0 -0
  53. {flowstate-0.6.2 → flowstate-0.7.0}/docs/assets/atmosphere_dens_comparison.png +0 -0
  54. {flowstate-0.6.2 → flowstate-0.7.0}/docs/assets/atmosphere_temp_comparison.png +0 -0
  55. {flowstate-0.6.2 → flowstate-0.7.0}/docs/assets/cira86_latitude_grid.png +0 -0
  56. {flowstate-0.6.2 → flowstate-0.7.0}/docs/assets/re1_hifire1.png +0 -0
  57. {flowstate-0.6.2 → flowstate-0.7.0}/docs/assets/re1_stort.png +0 -0
  58. {flowstate-0.6.2 → flowstate-0.7.0}/docs/cli-reference.md +0 -0
  59. {flowstate-0.6.2 → flowstate-0.7.0}/docs/configs/aedc_t9.toml +0 -0
  60. {flowstate-0.6.2 → flowstate-0.7.0}/docs/configs/bam6qt.toml +0 -0
  61. {flowstate-0.6.2 → flowstate-0.7.0}/docs/configs/hifire1.toml +0 -0
  62. {flowstate-0.6.2 → flowstate-0.7.0}/docs/configs/stort_cira.toml +0 -0
  63. {flowstate-0.6.2 → flowstate-0.7.0}/docs/examples.md +0 -0
  64. {flowstate-0.6.2 → flowstate-0.7.0}/docs/index.md +0 -0
  65. {flowstate-0.6.2 → flowstate-0.7.0}/docs/installation.md +0 -0
  66. {flowstate-0.6.2 → flowstate-0.7.0}/docs/javascripts/mathjax.js +0 -0
  67. {flowstate-0.6.2 → flowstate-0.7.0}/docs/nomenclature.md +0 -0
  68. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/atmosphere-comparison.md +0 -0
  69. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/atmosphere-models.md +0 -0
  70. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/definitions.md +0 -0
  71. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/gas-models.md +0 -0
  72. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/gas-properties.md +0 -0
  73. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/index.md +0 -0
  74. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/isentropic.md +0 -0
  75. {flowstate-0.6.2 → flowstate-0.7.0}/docs/theory/transport.md +0 -0
  76. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/basic-usage.md +0 -0
  77. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/cli.md +0 -0
  78. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/config-files.md +0 -0
  79. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/configuration.md +0 -0
  80. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/index.md +0 -0
  81. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/output.md +0 -0
  82. {flowstate-0.6.2 → flowstate-0.7.0}/docs/user-guide/workflow.md +0 -0
  83. {flowstate-0.6.2 → flowstate-0.7.0}/docs/vnv/index.md +0 -0
  84. {flowstate-0.6.2 → flowstate-0.7.0}/docs/vnv/validation/index.md +0 -0
  85. {flowstate-0.6.2 → flowstate-0.7.0}/docs/vnv/verification/index.md +0 -0
  86. {flowstate-0.6.2 → flowstate-0.7.0}/examples/bam6qt_conditions.py +0 -0
  87. {flowstate-0.6.2 → flowstate-0.7.0}/examples/flight_conditions_cira86.py +0 -0
  88. {flowstate-0.6.2 → flowstate-0.7.0}/examples/flight_conditions_ussa76.py +0 -0
  89. {flowstate-0.6.2 → flowstate-0.7.0}/examples/flow_config_cira86.toml +0 -0
  90. {flowstate-0.6.2 → flowstate-0.7.0}/examples/mach8_re1_conditions.py +0 -0
  91. {flowstate-0.6.2 → flowstate-0.7.0}/mkdocs.yml +0 -0
  92. {flowstate-0.6.2 → flowstate-0.7.0}/scripts/atmosphere_density_plotter.py +0 -0
  93. {flowstate-0.6.2 → flowstate-0.7.0}/scripts/generate_atmosphere_plots.py +0 -0
  94. {flowstate-0.6.2 → flowstate-0.7.0}/scripts/generate_latitude_grid.py +0 -0
  95. {flowstate-0.6.2 → flowstate-0.7.0}/scripts/stort_trajectory_reynolds_number_comparison.py +0 -0
  96. {flowstate-0.6.2 → flowstate-0.7.0}/scripts/temp_graph_atmosphere_models.py +0 -0
  97. {flowstate-0.6.2 → flowstate-0.7.0}/scripts/test_trajectory_1_calculation.py +0 -0
  98. {flowstate-0.6.2 → flowstate-0.7.0}/setup.cfg +0 -0
  99. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/__init__.py +0 -0
  100. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/atmosphere/__init__.py +0 -0
  101. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/atmosphere/cira86.py +0 -0
  102. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/atmosphere/core.py +0 -0
  103. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/atmosphere/registry.py +0 -0
  104. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/atmosphere/ussa76.py +0 -0
  105. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/core.py +0 -0
  106. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/gas/__init__.py +0 -0
  107. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/gas/equilibrium.py +0 -0
  108. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/gas/park.py +0 -0
  109. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/gas/perfect.py +0 -0
  110. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/gas/registry.py +0 -0
  111. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/io/__init__.py +0 -0
  112. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/io/hdf5.py +0 -0
  113. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/io/legacy_dat.py +0 -0
  114. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/io/print_summary.py +0 -0
  115. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/io/read_data.py +0 -0
  116. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/io/write_data.py +0 -0
  117. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/isentropic.py +0 -0
  118. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/math_utils/__init__.py +0 -0
  119. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/math_utils/bisection.py +0 -0
  120. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/recover.py +0 -0
  121. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/solvers.py +0 -0
  122. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/transport/__init__.py +0 -0
  123. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/transport/core.py +0 -0
  124. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/transport/keyes.py +0 -0
  125. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/transport/power_law.py +0 -0
  126. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/transport/registry.py +0 -0
  127. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/transport/sutherland.py +0 -0
  128. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/turbulence.py +0 -0
  129. {flowstate-0.6.2 → flowstate-0.7.0}/src/flow_state/units.py +0 -0
  130. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/dependency_links.txt +0 -0
  131. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/entry_points.txt +0 -0
  132. {flowstate-0.6.2 → flowstate-0.7.0}/src/flowstate.egg-info/top_level.txt +0 -0
  133. {flowstate-0.6.2 → flowstate-0.7.0}/stort_trajectory_reynolds_number_comparison.py +0 -0
  134. {flowstate-0.6.2 → flowstate-0.7.0}/tests/__init__.py +0 -0
  135. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_atmosphere.py +0 -0
  136. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_builders.py +0 -0
  137. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_equilibrium.py +0 -0
  138. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_gas.py +0 -0
  139. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_isentropic.py +0 -0
  140. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_park.py +0 -0
  141. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_recover.py +0 -0
  142. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_solvers.py +0 -0
  143. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_transport.py +0 -0
  144. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_turbulence.py +0 -0
  145. {flowstate-0.6.2 → flowstate-0.7.0}/tests/test_units.py +0 -0
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  strategy:
12
12
  matrix:
13
- python-version: ["3.11", "3.12", "3.13"]
13
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
14
14
  steps:
15
15
  - uses: actions/checkout@v6
16
16
 
@@ -11,7 +11,7 @@ jobs:
11
11
  runs-on: ubuntu-latest
12
12
  strategy:
13
13
  matrix:
14
- python-version: ["3.11", "3.12", "3.13"]
14
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
15
15
 
16
16
  steps:
17
17
  - uses: actions/checkout@v6
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowstate
3
- Version: 0.6.2
3
+ Version: 0.7.0
4
4
  Summary: Lightweight utility for computing flow states
5
5
  Author-email: Christoph Hader <christoph.hader@gmail.com>
6
6
  License-Expression: GPL-3.0-or-later
@@ -13,6 +13,8 @@ Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
16
18
  Classifier: Topic :: Scientific/Engineering :: Physics
17
19
  Requires-Python: >=3.11
18
20
  Description-Content-Type: text/markdown
@@ -20,6 +22,7 @@ License-File: LICENSE
20
22
  Requires-Dist: h5py>=3.10.0
21
23
  Requires-Dist: typer>=0.9.0
22
24
  Requires-Dist: tomli-w>=1.0.0
25
+ Requires-Dist: pydantic>=2.0
23
26
  Provides-Extra: dev
24
27
  Requires-Dist: pytest>=7.0; extra == "dev"
25
28
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
@@ -105,7 +108,7 @@ contributors can focus on the physics rather than inventing formatting rules.
105
108
  |---|---|---|
106
109
  | [PEP 8](https://peps.python.org/pep-0008/) | Code formatting, naming, whitespace | Python standard style guide |
107
110
  | [PEP 257](https://peps.python.org/pep-0257/) | Docstring structure (triple-quoted, imperative mood) | Python standard docstring conventions |
108
- | [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) | Docstring sections (`Parameters`, `Returns`, `Attributes`) | NumPy/SciPy docstring standard — the norm for scientific Python |
111
+ | [Google style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) | Docstring sections (`Args`, `Returns`, `Raises`) | Google Python style guide |
109
112
  | [Ruff](https://docs.astral.sh/ruff/) | Automated linting and formatting | Enforces PEP 8 compliance automatically |
110
113
  | [typing / TYPE_CHECKING](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING) | Type hints for IDE support and static analysis | Python standard library |
111
114
 
@@ -75,7 +75,7 @@ contributors can focus on the physics rather than inventing formatting rules.
75
75
  |---|---|---|
76
76
  | [PEP 8](https://peps.python.org/pep-0008/) | Code formatting, naming, whitespace | Python standard style guide |
77
77
  | [PEP 257](https://peps.python.org/pep-0257/) | Docstring structure (triple-quoted, imperative mood) | Python standard docstring conventions |
78
- | [numpydoc](https://numpydoc.readthedocs.io/en/latest/format.html) | Docstring sections (`Parameters`, `Returns`, `Attributes`) | NumPy/SciPy docstring standard — the norm for scientific Python |
78
+ | [Google style](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings) | Docstring sections (`Args`, `Returns`, `Raises`) | Google Python style guide |
79
79
  | [Ruff](https://docs.astral.sh/ruff/) | Automated linting and formatting | Enforces PEP 8 compliance automatically |
80
80
  | [typing / TYPE_CHECKING](https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING) | Type hints for IDE support and static analysis | Python standard library |
81
81
 
@@ -20,6 +20,8 @@ classifiers = [
20
20
  "Programming Language :: Python :: 3",
21
21
  "Programming Language :: Python :: 3.11",
22
22
  "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
23
25
  "Topic :: Scientific/Engineering :: Physics",
24
26
  ]
25
27
  keywords = ["compressible-flow", "aerodynamics", "gas-dynamics", "cfd"]
@@ -28,6 +30,7 @@ dependencies = [
28
30
  "h5py>=3.10.0",
29
31
  "typer>=0.9.0",
30
32
  "tomli-w>=1.0.0",
33
+ "pydantic>=2.0",
31
34
  ]
32
35
 
33
36
  [project.optional-dependencies]
@@ -51,7 +54,7 @@ Documentation = "https://uahypersonics.github.io/flow-state"
51
54
  where = ["src"]
52
55
 
53
56
  [tool.setuptools.package-data]
54
- "flow_state" = ["examples/*.toml"]
57
+ "flow_state" = ["examples/*/*"]
55
58
 
56
59
  [tool.setuptools_scm]
57
60
 
@@ -0,0 +1,15 @@
1
+ """Command-line interface for flow-state."""
2
+
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
6
+ from flow_state.cli.app import app
7
+
8
+ # --------------------------------------------------
9
+ # cli public api
10
+ # --------------------------------------------------
11
+
12
+ # Preserve the existing ``flow_state.cli:cli`` console entry point.
13
+ cli = app
14
+
15
+ __all__ = ["app", "cli"]
@@ -0,0 +1,75 @@
1
+ """Typer application for the flow-state command-line interface."""
2
+
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
6
+ from __future__ import annotations
7
+
8
+ from typing import Annotated
9
+
10
+ import typer
11
+
12
+ from flow_state.cli.callbacks import verbose_callback, version_callback
13
+ from flow_state.cli.cmd_examples import cmd_examples
14
+ from flow_state.cli.cmd_init import cmd_init
15
+ from flow_state.cli.cmd_recover import cmd_recover
16
+ from flow_state.cli.cmd_run import cmd_run
17
+
18
+ # --------------------------------------------------
19
+ # app
20
+ # --------------------------------------------------
21
+ app = typer.Typer(
22
+ name="flow-state",
23
+ help="Compute flow states.",
24
+ add_completion=False,
25
+ no_args_is_help=True,
26
+ )
27
+
28
+
29
+ # --------------------------------------------------
30
+ # callback for --version and --verbose options
31
+ # version_callback and verbose_callback are defined in callbacks.py
32
+ # --------------------------------------------------
33
+ @app.callback()
34
+ def main(
35
+ version: Annotated[
36
+ bool | None,
37
+ typer.Option(
38
+ "--version",
39
+ "-V",
40
+ help="Show version and exit.",
41
+ callback=version_callback,
42
+ is_eager=True,
43
+ ),
44
+ ] = None,
45
+ verbose: Annotated[
46
+ bool,
47
+ typer.Option(
48
+ "--verbose",
49
+ "-v",
50
+ help="Enable verbose (debug) output.",
51
+ callback=verbose_callback,
52
+ ),
53
+ ] = False,
54
+ ) -> None:
55
+ """Compute flow states from freestream, stagnation, or atmospheric inputs."""
56
+
57
+ # both options act through their callbacks; nothing left to do here
58
+ del version, verbose
59
+
60
+
61
+ # --------------------------------------------------
62
+ # register subcommands
63
+ # notes:
64
+ # - the order at which they are registered is the order in which they display
65
+ # --------------------------------------------------
66
+ app.command(name="init")(cmd_init)
67
+ app.command(name="run")(cmd_run)
68
+ app.command(name="recover")(cmd_recover)
69
+ app.command(name="examples")(cmd_examples)
70
+
71
+ # --------------------------------------------------
72
+ # main entry point
73
+ # --------------------------------------------------
74
+ if __name__ == "__main__":
75
+ app()
@@ -0,0 +1,51 @@
1
+ """Shared callbacks for the flow-state command-line interface."""
2
+
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
6
+ from __future__ import annotations
7
+
8
+ import logging
9
+
10
+ import typer
11
+
12
+ from flow_state import __version__
13
+
14
+ # --------------------------------------------------
15
+ # module constants
16
+ # --------------------------------------------------
17
+
18
+ # loggers that --verbose switches to debug: this tool and the uahypersonics
19
+ # packages it calls
20
+ DEBUG_LOGGERS = ("flow_state",)
21
+
22
+
23
+ # --------------------------------------------------
24
+ # verbose callback
25
+ # --------------------------------------------------
26
+ def verbose_callback(value: bool) -> None:
27
+ """Configure console logging; --verbose adds debug output from our own packages.
28
+
29
+ Third-party libraries stay at the default level, so --verbose shows our
30
+ debug messages without theirs.
31
+ """
32
+
33
+ # default console logging for every logger
34
+ logging.basicConfig(level=logging.INFO, format="%(levelname)-8s %(message)s")
35
+
36
+ # debug output from this tool and the uahypersonics packages it calls
37
+ if value:
38
+ for logger_name in DEBUG_LOGGERS:
39
+ logging.getLogger(logger_name).setLevel(logging.DEBUG)
40
+
41
+
42
+ # --------------------------------------------------
43
+ # version callback
44
+ # --------------------------------------------------
45
+ def version_callback(value: bool) -> None:
46
+ """Print the installed package version and exit."""
47
+
48
+ # print the version and stop before any command runs
49
+ if value:
50
+ typer.echo(f"flow-state version {__version__}")
51
+ raise typer.Exit()
@@ -0,0 +1,63 @@
1
+ """List bundled example cases, or copy one into the current folder."""
2
+
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+ from typing import Annotated
10
+
11
+ import typer
12
+
13
+ from flow_state.io.examples import copy_example, list_examples
14
+
15
+
16
+ # --------------------------------------------------
17
+ # subcommand: examples
18
+ # --------------------------------------------------
19
+ def cmd_examples(
20
+ name: Annotated[
21
+ str | None,
22
+ typer.Argument(help="Example to copy into the current folder. Omit to list the examples."),
23
+ ] = None,
24
+ force: Annotated[
25
+ bool,
26
+ typer.Option("--force", "-f", help="Overwrite files of the same name in this folder."),
27
+ ] = False,
28
+ ) -> None:
29
+ """List the bundled examples, or copy one into the current folder to run."""
30
+
31
+ # list: name and description of every example
32
+ examples = list_examples()
33
+ if name is None:
34
+ width = max(len(example_name) for example_name in [*examples, "<name>"])
35
+ typer.echo("")
36
+ typer.echo(f" {'<name>':<{width}} description")
37
+ typer.echo(f" {'-'*width} {'-'*11}")
38
+ for example_name, description in examples.items():
39
+ typer.echo(f" {example_name:<{width}} {description}")
40
+ typer.echo("")
41
+ typer.echo("command sequence:")
42
+ typer.echo(" - check out the example")
43
+ typer.echo(" flow-state examples <name>")
44
+ typer.echo(" - run the example")
45
+ typer.echo(" flow-state run")
46
+ typer.echo("")
47
+ return
48
+
49
+ # copy the example into the current folder
50
+ try:
51
+ written = copy_example(name, Path("."), force=force)
52
+ except KeyError:
53
+ known = ", ".join(examples)
54
+ typer.echo(f"error: unknown example {name!r}; available: {known}", err=True)
55
+ raise typer.Exit(1) from None
56
+ except FileExistsError as error:
57
+ typer.echo(f"error: {error}", err=True)
58
+ raise typer.Exit(1) from None
59
+
60
+ # report the files and the next step
61
+ for path in written:
62
+ typer.echo(f"wrote {path}")
63
+ typer.echo("then: flow-state run")
@@ -1,5 +1,8 @@
1
1
  """Generate a starter flow-state configuration."""
2
2
 
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
3
6
  from __future__ import annotations
4
7
 
5
8
  from pathlib import Path
@@ -11,6 +14,9 @@ from flow_state.cli.constants import DEFAULT_CONFIG
11
14
  from flow_state.cli.templates import CONFIG_TEMPLATE
12
15
 
13
16
 
17
+ # --------------------------------------------------
18
+ # subcommand: init
19
+ # --------------------------------------------------
14
20
  def cmd_init(
15
21
  output: Annotated[
16
22
  Path,
@@ -23,10 +29,12 @@ def cmd_init(
23
29
  ) -> None:
24
30
  """Generate a documented template configuration file."""
25
31
 
32
+ # check: don't overwrite an existing configuration unless asked to
26
33
  if output.exists() and not force:
27
34
  typer.echo(f"Error: {output} already exists. Use --force to overwrite", err=True)
28
35
  raise typer.Exit(1)
29
36
 
37
+ # write the template and tell the user the next step
30
38
  output.write_text(CONFIG_TEMPLATE)
31
39
  typer.echo(f"Created {output}")
32
- typer.echo("Edit the file, then run: flow-state solve")
40
+ typer.echo("Edit the file, then run: flow-state run")
@@ -1,5 +1,8 @@
1
1
  """Recover and write flow conditions from HDF5 metadata."""
2
2
 
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
3
6
  from __future__ import annotations
4
7
 
5
8
  from pathlib import Path
@@ -12,6 +15,9 @@ from flow_state.io import write_json
12
15
  from flow_state.recover import recover_from_hdf5
13
16
 
14
17
 
18
+ # --------------------------------------------------
19
+ # subcommand: recover
20
+ # --------------------------------------------------
15
21
  def cmd_recover(
16
22
  input_file: Annotated[
17
23
  Path,
@@ -31,14 +37,19 @@ def cmd_recover(
31
37
  ] = False,
32
38
  ) -> None:
33
39
  """Recover flow_conditions.json from HDF5 root attributes."""
40
+
41
+ # read: rebuild the flow state from the HDF5 root attributes
34
42
  try:
35
43
  state = recover_from_hdf5(input_file)
36
44
  except (FileNotFoundError, OSError, TypeError, ValueError) as error:
37
45
  typer.echo(f"Error recovering flow state: {error}", err=True)
38
46
  raise typer.Exit(1) from error
39
47
 
48
+ # write the flow-state JSON
40
49
  write_json(state, output)
41
50
  typer.echo(f"Wrote {output}")
51
+
52
+ # print the summary unless --quiet
42
53
  if not quiet:
43
54
  typer.echo("")
44
55
  typer.echo(str(state))
@@ -1,5 +1,8 @@
1
1
  """Compute and write a flow state from CLI or TOML inputs."""
2
2
 
3
+ # --------------------------------------------------
4
+ # load necessary modules
5
+ # --------------------------------------------------
3
6
  from __future__ import annotations
4
7
 
5
8
  import logging
@@ -12,10 +15,16 @@ from flow_state.cli.constants import DEFAULT_CONFIG, DEFAULT_OUTPUT
12
15
  from flow_state.io import read_config, write_flow_conditions_dat, write_json
13
16
  from flow_state.solvers import solve
14
17
 
18
+ # --------------------------------------------------
19
+ # set up logger
20
+ # --------------------------------------------------
15
21
  logger = logging.getLogger(__name__)
16
22
 
17
23
 
18
- def cmd_solve(
24
+ # --------------------------------------------------
25
+ # subcommand: run
26
+ # --------------------------------------------------
27
+ def cmd_run(
19
28
  mach: Annotated[float | None, typer.Option("--mach", "-m", help="Mach number")] = None,
20
29
  pres_stag: Annotated[
21
30
  float | None,
@@ -93,8 +102,10 @@ def cmd_solve(
93
102
  ) -> None:
94
103
  """Compute a flow state from direct options or a configuration file."""
95
104
 
105
+ # check: direct options on the command line take precedence over a config file
96
106
  has_direct_options = any([mach, pres_stag, temp_stag, altitude, pres, temp, re1])
97
107
 
108
+ # build the solver inputs from the direct options given (units as (value, unit) tuples)
98
109
  if has_direct_options:
99
110
  solve_kwargs: dict[str, object] = {}
100
111
  if mach is not None:
@@ -118,7 +129,10 @@ def cmd_solve(
118
129
  if lref is not None:
119
130
  solve_kwargs["lref"] = lref
120
131
  else:
132
+ # read the solver inputs from the config file (default: flow_config.toml)
121
133
  config_path = config if config else Path(DEFAULT_CONFIG)
134
+
135
+ # check: the config exists, otherwise point to `flow-state init`
122
136
  if not config_path.exists():
123
137
  typer.echo(f"Error: {config_path} not found.", err=True)
124
138
  typer.echo(
@@ -127,32 +141,38 @@ def cmd_solve(
127
141
  )
128
142
  raise typer.Exit(1)
129
143
 
144
+ # read and parse the config
130
145
  try:
131
146
  solve_kwargs = read_config(config_path)
132
- except Exception as error:
147
+ except (OSError, ValueError) as error:
133
148
  typer.echo(f"Error parsing {config_path}: {error}", err=True)
134
149
  raise typer.Exit(1) from error
135
150
 
151
+ # compute the flow state
136
152
  try:
137
153
  state = solve(**solve_kwargs)
138
154
  except (ValueError, TypeError) as error:
139
155
  typer.echo(f"Error computing flow state: {error}", err=True)
140
156
  raise typer.Exit(1) from error
141
157
 
158
+ # debug output for devs
142
159
  if state.provenance:
143
160
  for key, value in state.provenance.items():
144
161
  logger.debug("%s: %s", key, value)
145
162
  logger.debug("gas_model: %s", state.gas_model)
146
163
  logger.debug("transport_model: %s", state.transport_model)
147
164
 
165
+ # write the flow-state JSON
148
166
  write_json(state, output)
149
167
  typer.echo(f"Wrote {output}")
150
168
 
169
+ # write the legacy flow_conditions.dat when asked for
151
170
  if dat:
152
171
  dat_path = Path("flow_conditions.dat")
153
172
  write_flow_conditions_dat(state, dat_path)
154
173
  typer.echo(f"Wrote {dat_path}")
155
174
 
175
+ # print the summary unless --quiet
156
176
  if not quiet:
157
177
  typer.echo("")
158
178
  typer.echo(str(state))
@@ -0,0 +1,11 @@
1
+ """Shared constants for flow-state CLI commands."""
2
+
3
+ # --------------------------------------------------
4
+ # default file names
5
+ # --------------------------------------------------
6
+
7
+ # configuration file written by `flow-state init` and read by `flow-state run`
8
+ DEFAULT_CONFIG = "flow_config.toml"
9
+
10
+ # flow-state JSON written by `flow-state run` and `flow-state recover`
11
+ DEFAULT_OUTPUT = "flow_conditions.json"
@@ -1,9 +1,14 @@
1
1
  """Configuration templates emitted by flow-state CLI commands."""
2
2
 
3
+ # --------------------------------------------------
4
+ # configuration template
5
+ # --------------------------------------------------
6
+
7
+ # starter flow_config.toml written by `flow-state init`
3
8
  CONFIG_TEMPLATE = """\
4
9
  # flow_state configuration file
5
10
  # =============================
6
- # Edit the values below and run: flow-state solve
11
+ # Edit the values below and run: flow-state run
7
12
 
8
13
  # Supported units:
9
14
  # pressure : Pa, psi, atm, bar, torr
@@ -1,7 +1,7 @@
1
1
  # --------------------------------------------------
2
2
  # AEDC Tunnel 9 (Arnold Engineering Development Complex)
3
3
  #
4
- # Run: flow-state solve --config flow_config.toml
4
+ # Run: flow-state run --config flow_config.toml
5
5
  # --------------------------------------------------
6
6
 
7
7
  # --------------------------------------------------
@@ -1,7 +1,7 @@
1
1
  # --------------------------------------------------
2
2
  # BAM6QT — Boeing/AFOSR Mach 6 Quiet Tunnel (Purdue University)
3
3
  #
4
- # Run: flow-state solve --config flow_config.toml
4
+ # Run: flow-state run --config flow_config.toml
5
5
  # --------------------------------------------------
6
6
 
7
7
  # --------------------------------------------------
@@ -1,7 +1,7 @@
1
1
  # --------------------------------------------------
2
2
  # Sandia Hypersonic Wind Tunnel
3
3
  #
4
- # Run: flow-state solve --config flow_config.toml
4
+ # Run: flow-state run --config flow_config.toml
5
5
  # --------------------------------------------------
6
6
 
7
7
  # --------------------------------------------------
@@ -1,7 +1,7 @@
1
1
  # --------------------------------------------------
2
2
  # STORT (Short Range Transition)
3
3
  #
4
- # Run: flow-state solve --config flow_config.toml
4
+ # Run: flow-state run --config flow_config.toml
5
5
  # --------------------------------------------------
6
6
 
7
7
  # --------------------------------------------------
@@ -0,0 +1,122 @@
1
+ """Bundled example cases for the flow-state CLI.
2
+
3
+ Each example is a folder under ``flow_state/examples/`` holding the files of
4
+ one case (here a ``flow_config.toml``). The folder name is the example's name
5
+ and the first text line of its config is the description, so the folders are
6
+ the registry: adding an example means adding a folder.
7
+
8
+ Adding a new example
9
+ --------------------
10
+ 1. Add ``src/flow_state/examples/<name>/flow_config.toml``.
11
+ 2. Start the file with a comment that names the case (shown in the listing).
12
+ """
13
+
14
+ # --------------------------------------------------
15
+ # load necessary modules
16
+ # --------------------------------------------------
17
+ from __future__ import annotations
18
+
19
+ from importlib.resources import files
20
+ from importlib.resources.abc import Traversable
21
+ from pathlib import Path
22
+
23
+ # --------------------------------------------------
24
+ # module constants
25
+ # --------------------------------------------------
26
+
27
+ # config file every example holds (run's default config name)
28
+ EXAMPLE_CONFIG = "flow_config.toml"
29
+
30
+
31
+ # --------------------------------------------------
32
+ # helpers
33
+ # --------------------------------------------------
34
+ def _examples_root() -> Traversable:
35
+ """Folder of the bundled examples inside the installed package."""
36
+ return files("flow_state") / "examples"
37
+
38
+
39
+ def _description(config_text: str) -> str:
40
+ """First text line of a config's header comment (dividers skipped)."""
41
+
42
+ # walk the leading comment block
43
+ for line in config_text.splitlines():
44
+ stripped = line.strip()
45
+
46
+ # the header ends at the first line that isn't a comment
47
+ if stripped and not stripped.startswith("#"):
48
+ break
49
+
50
+ # skip blank comment lines and dividers like "# -----"
51
+ text = stripped.lstrip("#").strip()
52
+ if text and set(text) - set("-=#"):
53
+ return text
54
+
55
+ return ""
56
+
57
+
58
+ # --------------------------------------------------
59
+ # public API
60
+ # --------------------------------------------------
61
+ def list_examples() -> dict[str, str]:
62
+ """Return a name -> description mapping of the bundled examples.
63
+
64
+ Returns:
65
+ Dict of {name: description}, sorted by name.
66
+ """
67
+
68
+ # every folder with a config is an example
69
+ examples: dict[str, str] = {}
70
+ for folder in sorted(_examples_root().iterdir(), key=lambda item: item.name):
71
+ config = folder / EXAMPLE_CONFIG
72
+ if folder.is_dir() and config.is_file():
73
+ examples[folder.name] = _description(config.read_text(encoding="utf-8"))
74
+
75
+ return examples
76
+
77
+
78
+ def copy_example(name: str, destination: str | Path, force: bool = False) -> list[Path]:
79
+ """Copy a bundled example's files into a folder.
80
+
81
+ Args:
82
+ name: Example name (see list_examples).
83
+ destination: Folder to copy the files into (created if missing).
84
+ force: Overwrite files of the same name in the folder.
85
+
86
+ Returns:
87
+ The paths of the written files.
88
+
89
+ Raises:
90
+ KeyError: If there is no example of that name.
91
+ FileExistsError: If a file of the example already exists there and force
92
+ is False (nothing is written then).
93
+ """
94
+
95
+ # check: a known example
96
+ if name not in list_examples():
97
+ raise KeyError(name)
98
+
99
+ # the example's files
100
+ source = _examples_root() / name
101
+ items = sorted(
102
+ (entry for entry in source.iterdir() if entry.is_file()),
103
+ key=lambda entry: entry.name,
104
+ )
105
+
106
+ # check: don't overwrite the user's files unless asked to (before writing any)
107
+ destination = Path(destination)
108
+ existing = [item.name for item in items if (destination / item.name).exists()]
109
+ if existing and not force:
110
+ raise FileExistsError(
111
+ f"would overwrite {', '.join(existing)} (use --force to overwrite)"
112
+ )
113
+
114
+ # copy every file of the example folder
115
+ destination.mkdir(parents=True, exist_ok=True)
116
+ written: list[Path] = []
117
+ for item in items:
118
+ target = destination / item.name
119
+ target.write_bytes(item.read_bytes())
120
+ written.append(target)
121
+
122
+ return written