oe-python-template 0.11.2__tar.gz → 0.12.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 (41) hide show
  1. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/.gitignore +7 -1
  2. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/PKG-INFO +50 -28
  3. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/README.md +43 -24
  4. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/pyproject.toml +34 -28
  5. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/api.py +2 -2
  6. oe_python_template-0.12.0/src/oe_python_template/cli.py +55 -0
  7. oe_python_template-0.12.0/src/oe_python_template/constants.py +13 -0
  8. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/__init__.py +11 -0
  9. oe_python_template-0.12.0/src/oe_python_template/hello/_gui.py +40 -0
  10. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/__init__.py +12 -1
  11. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_cli.py +64 -23
  12. oe_python_template-0.12.0/src/oe_python_template/system/_gui.py +20 -0
  13. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/__init__.py +14 -0
  14. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_constants.py +27 -8
  15. oe_python_template-0.12.0/src/oe_python_template/utils/_gui.py +174 -0
  16. oe_python_template-0.12.0/src/oe_python_template/utils/_notebook.py +61 -0
  17. oe_python_template-0.11.2/src/oe_python_template/cli.py +0 -22
  18. oe_python_template-0.11.2/src/oe_python_template/constants.py +0 -7
  19. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/LICENSE +0 -0
  20. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/__init__.py +0 -0
  21. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_api.py +0 -0
  22. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_cli.py +0 -0
  23. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_constants.py +0 -0
  24. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_models.py +0 -0
  25. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_service.py +0 -0
  26. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_settings.py +0 -0
  27. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_api.py +0 -0
  28. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_service.py +0 -0
  29. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_settings.py +0 -0
  30. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_api.py +0 -0
  31. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_cli.py +0 -0
  32. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_console.py +0 -0
  33. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_di.py +0 -0
  34. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_health.py +0 -0
  35. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_log.py +0 -0
  36. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_logfire.py +0 -0
  37. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_process.py +0 -0
  38. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_sentry.py +0 -0
  39. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_service.py +0 -0
  40. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_settings.py +0 -0
  41. {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/boot.py +0 -0
@@ -17,6 +17,10 @@ env/
17
17
  .secrets.json
18
18
  .act-env-secret
19
19
 
20
+ # More secrets
21
+ .ssh
22
+ .aws
23
+
20
24
  # Python virtual environment
21
25
  venv/
22
26
  .venv/
@@ -69,6 +73,7 @@ tmp/
69
73
  # Node temps
70
74
  node_modules/
71
75
 
76
+
72
77
  # AI workflow
73
78
  .fixme
74
79
 
@@ -80,4 +85,5 @@ node_modules/
80
85
  .vercel
81
86
 
82
87
 
83
- # Application specific
88
+
89
+ # Application specific
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template
3
- Version: 0.11.2
3
+ Version: 0.12.0
4
4
  Summary: 🧠 Copier template to scaffold Python projects compliant with best practices and modern tooling.
5
5
  Project-URL: Homepage, https://oe-python-template.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://oe-python-template.readthedocs.io/en/latest/
@@ -59,15 +59,18 @@ Requires-Dist: opentelemetry-instrumentation-tornado>=0.53b0
59
59
  Requires-Dist: opentelemetry-instrumentation-urllib3>=0.53b0
60
60
  Requires-Dist: opentelemetry-instrumentation-urllib>=0.53b0
61
61
  Requires-Dist: psutil>=7.0.0
62
- Requires-Dist: pydantic-settings>=2.8.1
62
+ Requires-Dist: pydantic-settings>=2.9.1
63
63
  Requires-Dist: pydantic>=2.11.3
64
- Requires-Dist: sentry-sdk>=2.25.1
64
+ Requires-Dist: sentry-sdk>=2.26.1
65
65
  Requires-Dist: typer>=0.15.1
66
66
  Requires-Dist: uptime>=3.0.1
67
+ Provides-Extra: app
68
+ Requires-Dist: nicegui>=2.15.0; extra == 'app'
69
+ Requires-Dist: pywebview>=5.4; extra == 'app'
67
70
  Provides-Extra: examples
68
71
  Requires-Dist: jinja2>=3.1.6; extra == 'examples'
69
72
  Requires-Dist: jupyter>=1.1.1; extra == 'examples'
70
- Requires-Dist: marimo>=0.12.8; extra == 'examples'
73
+ Requires-Dist: marimo>=0.13.0; extra == 'examples'
71
74
  Requires-Dist: streamlit>=1.44.1; extra == 'examples'
72
75
  Description-Content-Type: text/markdown
73
76
 
@@ -148,13 +151,15 @@ Projects generated with this template come with a comprehensive development tool
148
151
  17. Changelog and release notes generated with [git-cliff](https://git-cliff.org/)
149
152
  18. Documentation generated with [Sphinx](https://www.sphinx-doc.org/en/master/) including reference documentation for the library, CLI, and API
150
153
  19. Documentation published to [Read The Docs](https://readthedocs.org/) including generation of PDF and single page HTML versions
151
- 20. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
152
- 21. Python package published to [PyPI](https://pypi.org/)
153
- 22. Docker images published to [Docker.io](https://hub.docker.com/) and [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) with [artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds)
154
- 23. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
155
- 24. Settings for use with [VSCode](https://code.visualstudio.com/)
156
- 25. Settings and custom instructions for use with [GitHub Copilot](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot)
157
- 26. API deployed as serverless function to [Vercel](https://vercel.com/) (optional)
154
+ 20. Documentation including dynamic badges, setup instructions, contribution guide and security policy
155
+ 21. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
156
+ 22. Python package published to [PyPI](https://pypi.org/)
157
+ 23. Multi-stage build of fat and slim (no-extras) Docker images, app running nonroot
158
+ 24. Mult-arch Docker images published to [Docker.io](https://hub.docker.com/) and [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) with [artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds)
159
+ 25. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
160
+ 26. Settings for use with [VSCode](https://code.visualstudio.com/)
161
+ 27. Settings and custom instructions for use with [GitHub Copilot](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot)
162
+ 28. API deployed as serverless function to [Vercel](https://vercel.com/) (optional)
158
163
 
159
164
  ### Application Features
160
165
 
@@ -162,17 +167,20 @@ Beyond development tooling, projects generated with this template include the co
162
167
 
163
168
  1. Usable as library with "Hello" module exposing a simple service that can say "Hello, world!" and echo utterances.
164
169
  2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
165
- 3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
166
- 4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
167
- 5. Simple Web UI with [Streamlit](https://streamlit.io/)
168
- 6. Configuration to run the CLI and API in a Docker container including setup for [Docker Compose](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-docker-compose/)
170
+ 2. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
171
+ 3. Comfortable command-line interface (CLI) with
172
+ [Typer](https://typer.tiangolo.com/)
173
+ 4. Cross-platform Graphical User Interface (GUI) with
174
+ [NiceGUI](https://nicegui.io/) running in native window
175
+ 5. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
176
+ 6. Simple Web UI with [Streamlit](https://streamlit.io/)
169
177
  7. Validation and settings management with [pydantic](https://docs.pydantic.dev/)
170
- 8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
171
- 9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
172
- 10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
173
- 11. Hello service demonstrates use of custom real time metrics collected via Logfire
174
- 12. Modular architecture including auto-registration of services, CLI commands and API routes exposed by modules
175
- 13. Documentation including dynamic badges, setup instructions, contribution guide and security policy
178
+ 8. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
179
+ 9. Modular architecture including auto-registration of services, CLI commands, API routes and GUI pages exposed by domain modules
180
+ 10. System module providing aggregate health and info to the runtime, compiled settings, and further info provided by domain modules
181
+ 11. Health and Info available via command, webservice API (info passsword protected) and GUI
182
+ 12. Hello service demonstrates use of custom real time metrics collected via Logfire
183
+ 13. Configuration to run the CLI and API in a Docker container including setup for [Docker Compose](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-docker-compose/)
176
184
 
177
185
  Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
178
186
 
@@ -239,9 +247,12 @@ pip install oe-python-template # add dependency to your project
239
247
  Executing the command line interface (CLI) in an isolated Python environment is just as easy:
240
248
 
241
249
  ```shell
242
- uvx oe-python-template hello-world # prints "Hello, world! [..]"
243
- uvx oe-python-template serve # serves web API
244
- uvx oe-python-template serve --port=4711 # serves web API on port 4711
250
+ uvx oe-python-template hello world # prints "Hello, world! [..]"
251
+ uvx oe-python-template hello echo "Lorem Ipsum" # echos "Lorem Ipsum"
252
+ uvx oe-python-template gui # opens the graphical user interface (GUI)
253
+ uvx oe-python-template system serve # serves web API
254
+ uvx oe-python-template system serve --port=4711 # serves web API on port 4711
255
+ uvx oe-python-template system openapi # serves web API on port 4711
245
256
  ```
246
257
 
247
258
  Notes:
@@ -254,10 +265,11 @@ The CLI provides extensive help:
254
265
 
255
266
  ```shell
256
267
  uvx oe-python-template --help # all CLI commands
257
- uvx oe-python-template hello-world --help # help for specific command
258
- uvx oe-python-template echo --help
259
- uvx oe-python-template openapi --help
260
- uvx oe-python-template serve --help
268
+ uvx oe-python-template hello world --help # help for specific command
269
+ uvx oe-python-template hello echo --help
270
+ uvx oe-python-template gui --help
271
+ uvx oe-python-template system serve --help
272
+ uvx oe-python-template system openapi --help
261
273
  ```
262
274
 
263
275
 
@@ -381,6 +393,7 @@ uvx oe-python-template hello world
381
393
  uvx oe-python-template hello echo --help
382
394
  uvx oe-python-template hello echo "Lorem"
383
395
  uvx oe-python-template hello echo "Lorem" --json
396
+ uvx oe-python-template gui
384
397
  uvx oe-python-template system info
385
398
  uvx oe-python-template system health
386
399
  uvx oe-python-template system openapi
@@ -465,6 +478,15 @@ echo "Shutting down the API container ..."
465
478
  docker compose down
466
479
  ```
467
480
 
481
+ #### Slim
482
+
483
+ The default Docker image includes all extras. Additionally a slim image is provided, with no extras. Run as follows
484
+
485
+ ```shell
486
+ docker compose run --remove-orphans oe-python-template-slim --help
487
+ ```
488
+
489
+
468
490
  * See the [reference documentation of the API](https://oe-python-template.readthedocs.io/en/latest/api_reference_v1.html) for detailed documentation of all API operations and parameters.
469
491
 
470
492
 
@@ -75,13 +75,15 @@ Projects generated with this template come with a comprehensive development tool
75
75
  17. Changelog and release notes generated with [git-cliff](https://git-cliff.org/)
76
76
  18. Documentation generated with [Sphinx](https://www.sphinx-doc.org/en/master/) including reference documentation for the library, CLI, and API
77
77
  19. Documentation published to [Read The Docs](https://readthedocs.org/) including generation of PDF and single page HTML versions
78
- 20. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
79
- 21. Python package published to [PyPI](https://pypi.org/)
80
- 22. Docker images published to [Docker.io](https://hub.docker.com/) and [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) with [artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds)
81
- 23. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
82
- 24. Settings for use with [VSCode](https://code.visualstudio.com/)
83
- 25. Settings and custom instructions for use with [GitHub Copilot](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot)
84
- 26. API deployed as serverless function to [Vercel](https://vercel.com/) (optional)
78
+ 20. Documentation including dynamic badges, setup instructions, contribution guide and security policy
79
+ 21. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
80
+ 22. Python package published to [PyPI](https://pypi.org/)
81
+ 23. Multi-stage build of fat and slim (no-extras) Docker images, app running nonroot
82
+ 24. Mult-arch Docker images published to [Docker.io](https://hub.docker.com/) and [GitHub Container Registry](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry) with [artifact attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds)
83
+ 25. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
84
+ 26. Settings for use with [VSCode](https://code.visualstudio.com/)
85
+ 27. Settings and custom instructions for use with [GitHub Copilot](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot)
86
+ 28. API deployed as serverless function to [Vercel](https://vercel.com/) (optional)
85
87
 
86
88
  ### Application Features
87
89
 
@@ -89,17 +91,20 @@ Beyond development tooling, projects generated with this template include the co
89
91
 
90
92
  1. Usable as library with "Hello" module exposing a simple service that can say "Hello, world!" and echo utterances.
91
93
  2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
92
- 3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
93
- 4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
94
- 5. Simple Web UI with [Streamlit](https://streamlit.io/)
95
- 6. Configuration to run the CLI and API in a Docker container including setup for [Docker Compose](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-docker-compose/)
94
+ 2. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
95
+ 3. Comfortable command-line interface (CLI) with
96
+ [Typer](https://typer.tiangolo.com/)
97
+ 4. Cross-platform Graphical User Interface (GUI) with
98
+ [NiceGUI](https://nicegui.io/) running in native window
99
+ 5. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
100
+ 6. Simple Web UI with [Streamlit](https://streamlit.io/)
96
101
  7. Validation and settings management with [pydantic](https://docs.pydantic.dev/)
97
- 8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
98
- 9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
99
- 10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
100
- 11. Hello service demonstrates use of custom real time metrics collected via Logfire
101
- 12. Modular architecture including auto-registration of services, CLI commands and API routes exposed by modules
102
- 13. Documentation including dynamic badges, setup instructions, contribution guide and security policy
102
+ 8. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
103
+ 9. Modular architecture including auto-registration of services, CLI commands, API routes and GUI pages exposed by domain modules
104
+ 10. System module providing aggregate health and info to the runtime, compiled settings, and further info provided by domain modules
105
+ 11. Health and Info available via command, webservice API (info passsword protected) and GUI
106
+ 12. Hello service demonstrates use of custom real time metrics collected via Logfire
107
+ 13. Configuration to run the CLI and API in a Docker container including setup for [Docker Compose](https://docs.docker.com/get-started/docker-concepts/the-basics/what-is-docker-compose/)
103
108
 
104
109
  Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
105
110
 
@@ -166,9 +171,12 @@ pip install oe-python-template # add dependency to your project
166
171
  Executing the command line interface (CLI) in an isolated Python environment is just as easy:
167
172
 
168
173
  ```shell
169
- uvx oe-python-template hello-world # prints "Hello, world! [..]"
170
- uvx oe-python-template serve # serves web API
171
- uvx oe-python-template serve --port=4711 # serves web API on port 4711
174
+ uvx oe-python-template hello world # prints "Hello, world! [..]"
175
+ uvx oe-python-template hello echo "Lorem Ipsum" # echos "Lorem Ipsum"
176
+ uvx oe-python-template gui # opens the graphical user interface (GUI)
177
+ uvx oe-python-template system serve # serves web API
178
+ uvx oe-python-template system serve --port=4711 # serves web API on port 4711
179
+ uvx oe-python-template system openapi # serves web API on port 4711
172
180
  ```
173
181
 
174
182
  Notes:
@@ -181,10 +189,11 @@ The CLI provides extensive help:
181
189
 
182
190
  ```shell
183
191
  uvx oe-python-template --help # all CLI commands
184
- uvx oe-python-template hello-world --help # help for specific command
185
- uvx oe-python-template echo --help
186
- uvx oe-python-template openapi --help
187
- uvx oe-python-template serve --help
192
+ uvx oe-python-template hello world --help # help for specific command
193
+ uvx oe-python-template hello echo --help
194
+ uvx oe-python-template gui --help
195
+ uvx oe-python-template system serve --help
196
+ uvx oe-python-template system openapi --help
188
197
  ```
189
198
 
190
199
 
@@ -308,6 +317,7 @@ uvx oe-python-template hello world
308
317
  uvx oe-python-template hello echo --help
309
318
  uvx oe-python-template hello echo "Lorem"
310
319
  uvx oe-python-template hello echo "Lorem" --json
320
+ uvx oe-python-template gui
311
321
  uvx oe-python-template system info
312
322
  uvx oe-python-template system health
313
323
  uvx oe-python-template system openapi
@@ -392,6 +402,15 @@ echo "Shutting down the API container ..."
392
402
  docker compose down
393
403
  ```
394
404
 
405
+ #### Slim
406
+
407
+ The default Docker image includes all extras. Additionally a slim image is provided, with no extras. Run as follows
408
+
409
+ ```shell
410
+ docker compose run --remove-orphans oe-python-template-slim --help
411
+ ```
412
+
413
+
395
414
  * See the [reference documentation of the API](https://oe-python-template.readthedocs.io/en/latest/api_reference_v1.html) for detailed documentation of all API operations and parameters.
396
415
 
397
416
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oe-python-template"
3
- version = "0.11.2"
3
+ version = "0.12.0"
4
4
  description = "🧠 Copier template to scaffold Python projects compliant with best practices and modern tooling."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Helmut Hoffer von Ankershoffen", email = "helmuthva@gmail.com" }]
@@ -73,49 +73,30 @@ dependencies = [
73
73
  "opentelemetry-instrumentation-urllib3>=0.53b0",
74
74
  "psutil>=7.0.0",
75
75
  "pydantic>=2.11.3",
76
- "pydantic-settings>=2.8.1",
77
- "sentry-sdk>=2.25.1",
76
+ "pydantic-settings>=2.9.1",
77
+ "sentry-sdk>=2.26.1",
78
78
  "typer>=0.15.1",
79
79
  "uptime>=3.0.1",
80
80
  # Custom
81
81
  # Nothing yet
82
82
  ]
83
83
 
84
- [project.scripts]
85
- oe-python-template = "oe_python_template.cli:cli"
86
-
87
- [project.urls]
88
- Homepage = "https://oe-python-template.readthedocs.io/en/latest/"
89
- Documentation = "https://oe-python-template.readthedocs.io/en/latest/"
90
- Source = "https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template"
91
- Changelog = "https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template/releases"
92
- Issues = "https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template/issues"
93
-
94
- [build-system]
95
- requires = ["hatchling==1.27.0"]
96
- build-backend = "hatchling.build"
97
-
98
- [tool.hatch.build]
99
- include = ["src/*"]
100
-
101
- [tool.hatch.build.targets.wheel]
102
- packages = ["src/oe_python_template"]
103
-
104
84
  [project.optional-dependencies]
105
85
  examples = [
106
86
  "streamlit>=1.44.1",
107
- "marimo>=0.12.8",
87
+ "marimo>=0.13.0",
108
88
  "jupyter>=1.1.1",
109
89
  "jinja2>=3.1.6",
110
90
  ]
91
+ app = ["nicegui>=2.15.0", "pywebview>=5.4"]
111
92
 
112
93
  [dependency-groups]
113
94
  dev = [
114
95
  "autodoc-pydantic>=2.2.0",
115
- "bump-my-version>=1.1.1",
96
+ "bump-my-version>=1.1.2",
116
97
  "cyclonedx-py>=1.0.1",
117
98
  "detect-secrets>=1.5.0",
118
- "enum-tools>=0.12.0",
99
+ "enum-tools>=0.13.0",
119
100
  "furo>=2024.8.6",
120
101
  "git-cliff>=2.8.0",
121
102
  "matplotlib>=3.10.1",
@@ -131,10 +112,12 @@ dev = [
131
112
  "pytest-docker>=3.2.1",
132
113
  "pytest-env>=1.1.5",
133
114
  "pytest-regressions>=2.7.0",
115
+ "pytest-selenium>=4.1.0",
134
116
  "pytest-subprocess>=1.5.3",
135
117
  "pytest-timeout>=2.3.1",
118
+ "pytest-watcher>=0.4.3",
136
119
  "pytest-xdist[psutil]>=3.6.1",
137
- "ruff>=0.11.5",
120
+ "ruff>=0.11.6",
138
121
  "sphinx>=8.2.3",
139
122
  "sphinx-autobuild>=2024.10.3",
140
123
  "sphinx-copybutton>=0.5.2",
@@ -151,6 +134,26 @@ dev = [
151
134
  "watchdog>=6.0.0",
152
135
  ]
153
136
 
137
+ [project.scripts]
138
+ oe-python-template = "oe_python_template.cli:cli"
139
+
140
+ [project.urls]
141
+ Homepage = "https://oe-python-template.readthedocs.io/en/latest/"
142
+ Documentation = "https://oe-python-template.readthedocs.io/en/latest/"
143
+ Source = "https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template"
144
+ Changelog = "https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template/releases"
145
+ Issues = "https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template/issues"
146
+
147
+ [build-system]
148
+ requires = ["hatchling==1.27.0"]
149
+ build-backend = "hatchling.build"
150
+
151
+ [tool.hatch.build]
152
+ include = ["src/*"]
153
+
154
+ [tool.hatch.build.targets.wheel]
155
+ packages = ["src/oe_python_template"]
156
+
154
157
  [tool.uv]
155
158
  override-dependencies = [ # https://github.com/astral-sh/uv/issues/4422
156
159
  "rfc3987; sys_platform == 'never'", # GPLv3
@@ -276,7 +279,7 @@ source = ["src/"]
276
279
 
277
280
 
278
281
  [tool.bumpversion]
279
- current_version = "0.11.2"
282
+ current_version = "0.12.0"
280
283
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
281
284
  serialize = ["{major}.{minor}.{patch}"]
282
285
  search = "{current_version}"
@@ -314,6 +317,9 @@ filename = "sonar-project.properties"
314
317
  [[tool.bumpversion.files]]
315
318
  filename = "docs/source/conf.py"
316
319
 
320
+ [[tool.bumpversion.files]]
321
+ filename = "examples/notebook.py"
322
+
317
323
  [tool.git-cliff.remote.github]
318
324
  owner = "helmut-hoffer-von-ankershoffen"
319
325
  repo = "oe-python-template"
@@ -31,7 +31,7 @@ API_BASE_URL = __base__url__
31
31
  if not API_BASE_URL:
32
32
  API_BASE_URL = f"http://{UVICORN_HOST}:{UVICORN_PORT}"
33
33
 
34
- app = FastAPI(
34
+ api = FastAPI(
35
35
  root_path="/api",
36
36
  title=TITLE,
37
37
  contact={
@@ -76,4 +76,4 @@ for router in locate_implementations(VersionedAPIRouter):
76
76
 
77
77
  # Mount all API versions to the main app
78
78
  for version in API_VERSIONS:
79
- app.mount(f"/{version}", api_instances[version])
79
+ api.mount(f"/{version}", api_instances[version])
@@ -0,0 +1,55 @@
1
+ """CLI (Command Line Interface) of OE Python Template."""
2
+
3
+ import sys
4
+ from importlib.util import find_spec
5
+
6
+ import typer
7
+
8
+ from .constants import MODULES_TO_INSTRUMENT
9
+ from .utils import __version__, boot, console, get_logger, prepare_cli
10
+
11
+ boot(MODULES_TO_INSTRUMENT)
12
+ logger = get_logger(__name__)
13
+
14
+ cli = typer.Typer(help="Command Line Interface of OE Python Template")
15
+ prepare_cli(cli, f"🧠 OE Python Template v{__version__} - built with love in Berlin 🐻")
16
+
17
+
18
+ if find_spec("nicegui"):
19
+
20
+ @cli.command()
21
+ def gui() -> None:
22
+ """Start graphical user interface (GUI) in native window."""
23
+ from .utils import gui_run # noqa: PLC0415
24
+
25
+ gui_run(native=True, with_api=False, title="OE Python Template", icon="🧠")
26
+
27
+
28
+ if find_spec("marimo"):
29
+ from typing import Annotated
30
+
31
+ import uvicorn
32
+
33
+ from .utils import create_marimo_app
34
+
35
+ @cli.command()
36
+ def notebook(
37
+ host: Annotated[str, typer.Option(help="Host to bind the server to")] = "127.0.0.1",
38
+ port: Annotated[int, typer.Option(help="Port to bind the server to")] = 8001,
39
+ ) -> None:
40
+ """Start notebook in web browser."""
41
+ console.print(f"Starting marimo notebook server at http://{host}:{port}")
42
+ uvicorn.run(
43
+ create_marimo_app(),
44
+ host=host,
45
+ port=port,
46
+ )
47
+
48
+
49
+ if __name__ == "__main__": # pragma: no cover
50
+ try:
51
+ cli()
52
+ except Exception as e: # noqa: BLE001
53
+ logger.critical("Fatal error occurred: %s", e)
54
+ console.print(f"Fatal error occurred: {e}", style="error")
55
+ sys.exit(1)
@@ -0,0 +1,13 @@
1
+ """Constants for the OE Python Template."""
2
+
3
+ from pathlib import Path
4
+
5
+ MODULES_TO_INSTRUMENT = ["oe_python_template.hello"]
6
+
7
+ API_VERSIONS = {
8
+ "v1": "1.0.0",
9
+ "v2": "2.0.0",
10
+ }
11
+
12
+ NOTEBOOK_FOLDER = Path(__file__).parent.parent.parent / "examples"
13
+ NOTEBOOK_APP = Path(__file__).parent.parent.parent / "examples" / "notebook.py"
@@ -15,3 +15,14 @@ __all__ = [
15
15
  "api_v2",
16
16
  "cli",
17
17
  ]
18
+
19
+
20
+ from importlib.util import find_spec
21
+
22
+ # advertise PageBuuilder to enable auto-discovery
23
+ if find_spec("nicegui"):
24
+ from ._gui import PageBuilder
25
+
26
+ __all__ += [
27
+ "PageBuilder",
28
+ ]
@@ -0,0 +1,40 @@
1
+ """Homepage (index) of GUI."""
2
+
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ from nicegui import ui
7
+
8
+ from oe_python_template.utils import BasePageBuilder, GUILocalFilePicker
9
+
10
+ from ._service import Service
11
+
12
+
13
+ async def pick_file() -> None:
14
+ """Open a file picker dialog and show notifier when closed again."""
15
+ result = await GUILocalFilePicker(str(Path.cwd() / "examples"), multiple=True)
16
+ ui.notify(f"You chose {result}")
17
+
18
+
19
+ class PageBuilder(BasePageBuilder):
20
+ @staticmethod
21
+ def register_pages() -> None:
22
+ @ui.page("/")
23
+ def page_index() -> None:
24
+ """Homepage of GUI."""
25
+ service = Service()
26
+
27
+ ui.button("Choose file", on_click=pick_file, icon="folder").mark("BUTTON_CHOOSE_FILE")
28
+
29
+ ui.button("Click me", on_click=lambda: ui.notify(service.get_hello_world()), icon="check").mark(
30
+ "BUTTON_CLICK_ME"
31
+ )
32
+
33
+ with ui.card().tight().mark("CARD_PLOT"): # noqa: SIM117
34
+ with ui.matplotlib(figsize=(4, 3)).figure as fig:
35
+ x = np.linspace(0.0, 5.0)
36
+ y = np.cos(2 * np.pi * x) * np.exp(-x)
37
+ ax = fig.gca()
38
+ ax.plot(x, y, "-")
39
+
40
+ ui.link("Info", "/info").mark("LINK_INFO")
@@ -1,4 +1,4 @@
1
- """Hello module."""
1
+ """System module."""
2
2
 
3
3
  from ._api import api_routers
4
4
  from ._cli import cli
@@ -12,6 +12,17 @@ __all__ = [
12
12
  "cli",
13
13
  ]
14
14
 
15
+
16
+ from importlib.util import find_spec
17
+
18
+ # advertise PageBuuilder to enable auto-discovery
19
+ if find_spec("nicegui"):
20
+ from ._gui import PageBuilder
21
+
22
+ __all__ += [
23
+ "PageBuilder",
24
+ ]
25
+
15
26
  # Export all individual API routers so they are picked up by depdency injection (DI)
16
27
  for version, router in api_routers.items():
17
28
  router_name = f"api_{version}"
@@ -3,6 +3,7 @@
3
3
  import json
4
4
  import os
5
5
  from enum import StrEnum
6
+ from importlib.util import find_spec
6
7
  from typing import Annotated
7
8
 
8
9
  import typer
@@ -81,29 +82,69 @@ def info(
81
82
  console.print(yaml.dump(info, width=80, default_flow_style=False), end="")
82
83
 
83
84
 
84
- @cli.command()
85
- def serve(
86
- host: Annotated[str, typer.Option(help="Host to bind the server to")] = "127.0.0.1",
87
- port: Annotated[int, typer.Option(help="Port to bind the server to")] = 8000,
88
- watch: Annotated[bool, typer.Option(help="Enable auto-reload")] = True,
89
- ) -> None:
90
- """Start the webservice API server.
91
-
92
- Args:
93
- host (str): Host to bind the server to.
94
- port (int): Port to bind the server to.
95
- watch (bool): Enable auto-reload.
96
- """
97
- console.print(f"Starting API server at http://{host}:{port}")
98
- # using environ to pass host/port to api.py to generate doc link
99
- os.environ["UVICORN_HOST"] = host
100
- os.environ["UVICORN_PORT"] = str(port)
101
- uvicorn.run(
102
- f"{__project_name__}.api:app",
103
- host=host,
104
- port=port,
105
- reload=watch,
106
- )
85
+ if find_spec("nicegui"):
86
+ from ..utils import gui_run # noqa: TID252
87
+
88
+ @cli.command()
89
+ def serve( # noqa: PLR0913, PLR0917 # type: ignore
90
+ app: Annotated[bool, typer.Option(help="Enable web application")] = True,
91
+ api: Annotated[bool, typer.Option(help="Enable webservice API")] = True,
92
+ host: Annotated[str, typer.Option(help="Host to bind the server to")] = "127.0.0.1",
93
+ port: Annotated[int, typer.Option(help="Port to bind the server to")] = 8000,
94
+ watch: Annotated[bool, typer.Option(help="Enable auto-reload on changes of source code")] = True,
95
+ open_browser: Annotated[bool, typer.Option(help="Open app in browser after starting the server")] = False,
96
+ ) -> None:
97
+ """Start the web server, hosting the graphical web application and/or webservice API.
98
+
99
+ Args:
100
+ app (bool): Enable web application.
101
+ api (bool): Enable webservice API.
102
+ host (str): Host to bind the server to.
103
+ port (int): Port to bind the server to.
104
+ watch (bool): Enable auto-reload on changes of source code.
105
+ open_browser (bool): Open app in browser after starting the server.
106
+ """
107
+ if api and not app:
108
+ console.print(f"Starting webservice API server at http://{host}:{port}")
109
+ # using environ to pass host/port to api.py to generate doc link
110
+ os.environ["UVICORN_HOST"] = host
111
+ os.environ["UVICORN_PORT"] = str(port)
112
+ uvicorn.run(
113
+ f"{__project_name__}.api:api",
114
+ host=host,
115
+ port=port,
116
+ reload=watch,
117
+ )
118
+ elif app:
119
+ console.print(f"Starting web application server at http://{host}:{port}")
120
+ gui_run(native=False, host=host, port=port, with_api=api, show=open_browser)
121
+
122
+ else:
123
+
124
+ @cli.command()
125
+ def serve( # type: ignore
126
+ host: Annotated[str, typer.Option(help="Host to bind the server to")] = "127.0.0.1",
127
+ port: Annotated[int, typer.Option(help="Port to bind the server to")] = 8000,
128
+ watch: Annotated[bool, typer.Option(help="Enable auto-reload on changes of source code")] = True,
129
+ ) -> None:
130
+ """Start the web server, hosting the API.
131
+
132
+ Args:
133
+ api (bool): Enable webservice API.
134
+ host (str): Host to bind the server to.
135
+ port (int): Port to bind the server to.
136
+ watch (bool): Enable auto-reload on changes of source code.
137
+ """
138
+ console.print(f"Starting webservice API server at http://{host}:{port}")
139
+ # using environ to pass host/port to api.py to generate doc link
140
+ os.environ["UVICORN_HOST"] = host
141
+ os.environ["UVICORN_PORT"] = str(port)
142
+ uvicorn.run(
143
+ f"{__project_name__}.api:api",
144
+ host=host,
145
+ port=port,
146
+ reload=watch,
147
+ )
107
148
 
108
149
 
109
150
  @cli.command()
@@ -0,0 +1,20 @@
1
+ """Homepage (index) of GUI."""
2
+
3
+ from nicegui import ui
4
+
5
+ from ..utils import BasePageBuilder, __project_name__, __version__ # noqa: TID252
6
+ from ._service import Service
7
+
8
+
9
+ class PageBuilder(BasePageBuilder):
10
+ @staticmethod
11
+ def register_pages() -> None:
12
+ @ui.page("/info")
13
+ def page_info() -> None:
14
+ """Homepage of GUI."""
15
+ ui.label(f"{__project_name__} v{__version__}").mark("LABEL_VERSION")
16
+ ui.json_editor({
17
+ "content": {"json": Service().info(True, True)},
18
+ "readOnly": True,
19
+ }).mark("JSON_EDITOR_INFO")
20
+ ui.link("Home", "/").mark("LINK_HOME")
@@ -60,3 +60,17 @@ __all__ = [
60
60
  "prepare_cli",
61
61
  "strip_to_none_before_validator",
62
62
  ]
63
+
64
+ from importlib.util import find_spec
65
+
66
+ if find_spec("nicegui"):
67
+ from ._gui import BasePageBuilder, GUILocalFilePicker, gui_register_pages, gui_run
68
+
69
+ __all__ += ["BasePageBuilder", "GUILocalFilePicker", "gui_register_pages", "gui_run"]
70
+
71
+ if find_spec("marimo"):
72
+ from ._notebook import create_marimo_app
73
+
74
+ __all__ += [
75
+ "create_marimo_app",
76
+ ]
@@ -14,7 +14,21 @@ __project_path__ = str(Path(__file__).parent.parent.parent)
14
14
  __version__ = metadata.version(__project_name__)
15
15
  __is_development_mode__ = "uvx" not in sys.argv[0].lower()
16
16
  __is_running_in_container__ = os.getenv(f"{__project_name__.upper()}_RUNNING_IN_CONTAINER")
17
- __env__ = os.getenv("ENV", os.getenv("VERCEL_ENV", "local"))
17
+
18
+ # Determine environment we are deployed on
19
+ ENV_VAR_MAPPINGS = {
20
+ "ENV": lambda env: env,
21
+ "VERCEL_ENV": lambda env: env, # See https://vercel.com/docs/environment-variables/system-environment-variables
22
+ "RAILWAY_ENVIRONMENT": lambda env: env, # See https://docs.railway.com/reference/variables#railway-provided-variables
23
+ }
24
+ __env__ = "local" # Default
25
+ for env_var, mapper in ENV_VAR_MAPPINGS.items():
26
+ env_value = os.getenv(env_var)
27
+ if env_value:
28
+ __env__ = mapper(env_value) # type: ignore[no-untyped-call]
29
+ break
30
+
31
+ # Define environment file paths
18
32
  __env_file__ = [
19
33
  Path.home() / f".{__project_name__}" / ".env",
20
34
  Path.home() / f".{__project_name__}" / f".env.{__env__}",
@@ -25,12 +39,18 @@ env_file_path = os.getenv(f"{__project_name__.upper()}_ENV_FILE")
25
39
  if env_file_path:
26
40
  __env_file__.insert(2, Path(env_file_path))
27
41
 
28
- vercel_base_url = os.getenv("VERCEL_URL", None)
29
- if vercel_base_url:
30
- vercel_base_url = "https://" + vercel_base_url
31
- __base__url__ = os.getenv(__project_name__.upper() + "_BASE_URL", None)
32
- if not __base__url__ and vercel_base_url:
33
- __base__url__ = vercel_base_url
42
+ # Determine __base_url__
43
+ PLATFORM_URL_MAPPINGS = {
44
+ "VERCEL_URL": lambda url: f"https://{url}", # See https://vercel.com/docs/environment-variables/system-environment-variables
45
+ "RAILWAY_PUBLIC_DOMAIN": lambda url: f"https://{url}", # See https://docs.railway.com/reference/variables#railway-provided-variables
46
+ }
47
+ __base__url__ = os.getenv(f"{__project_name__.upper()}_BASE_URL")
48
+ if not __base__url__:
49
+ for env_var, mappers in PLATFORM_URL_MAPPINGS.items():
50
+ env_value = os.getenv(env_var)
51
+ if env_value:
52
+ __base__url__ = mappers(env_value) # type: ignore[no-untyped-call]
53
+ break
34
54
 
35
55
 
36
56
  def get_project_url_by_label(prefix: str) -> str:
@@ -47,7 +67,6 @@ def get_project_url_by_label(prefix: str) -> str:
47
67
  for url_entry in metadata.metadata(__project_name__).get_all("Project-URL", []):
48
68
  if url_entry.startswith(prefix):
49
69
  return str(url_entry.split(", ", 1)[1])
50
-
51
70
  return ""
52
71
 
53
72
 
@@ -0,0 +1,174 @@
1
+ import platform
2
+ from abc import ABC, abstractmethod
3
+ from pathlib import Path
4
+ from types import EllipsisType
5
+
6
+ from nicegui import app, events, ui
7
+ from nicegui import native as native_app
8
+
9
+ from ._constants import __project_name__
10
+ from ._di import locate_subclasses
11
+ from ._log import get_logger
12
+
13
+ logger = get_logger(__name__)
14
+
15
+
16
+ class BasePageBuilder(ABC):
17
+ """Base class for all page builders."""
18
+
19
+ @staticmethod
20
+ @abstractmethod
21
+ def register_pages() -> None:
22
+ """Register pages."""
23
+
24
+
25
+ def gui_register_pages() -> None:
26
+ """Register pages.
27
+
28
+ This function is called by the GUI to register all pages.
29
+ """
30
+ page_builders = locate_subclasses(BasePageBuilder)
31
+ for page_builder in page_builders:
32
+ page_builder.register_pages()
33
+
34
+
35
+ def gui_run( # noqa: PLR0913, PLR0917
36
+ native: bool = True,
37
+ show: bool = False,
38
+ host: str | None = None,
39
+ port: int | None = None,
40
+ title: str = __project_name__,
41
+ icon: str = "",
42
+ watch: bool = False,
43
+ with_api: bool = False,
44
+ ) -> None:
45
+ """Start the GUI.
46
+
47
+ Args:
48
+ native: Whether to run the GUI in native mode.
49
+ show: Whether to show the GUI.
50
+ host: Host to run the GUI on.
51
+ port: Port to run the GUI on.
52
+ title: Title of the GUI.
53
+ icon: Icon for the GUI.
54
+ watch: Whether to watch for changes and reload the GUI.
55
+ with_api: Whether to mount the API.
56
+
57
+ Raises:
58
+ ValueError: If with_notebook is True but notebook_path is None.
59
+ """
60
+ if with_api:
61
+ from ..api import api # noqa: PLC0415, TID252
62
+
63
+ app.mount("/api", api)
64
+
65
+ gui_register_pages()
66
+ ui.run(
67
+ title=title,
68
+ favicon=icon,
69
+ native=native,
70
+ reload=watch,
71
+ dark=False,
72
+ host=host,
73
+ port=port or native_app.find_open_port(),
74
+ frameless=False,
75
+ show_welcome_message=True,
76
+ show=show,
77
+ )
78
+
79
+
80
+ class GUILocalFilePicker(ui.dialog):
81
+ def __init__(
82
+ self,
83
+ directory: str,
84
+ *,
85
+ upper_limit: str | EllipsisType | None = ...,
86
+ multiple: bool = False,
87
+ show_hidden_files: bool = False,
88
+ ) -> None:
89
+ """Local File Picker.
90
+
91
+ A simple file picker that allows selecting files from the local filesystem where NiceGUI is running.
92
+
93
+ Args:
94
+ directory: The directory to start in.
95
+ upper_limit: The directory to stop at. None for no limit, default is same as starting directory.
96
+ multiple: Whether to allow multiple files to be selected.
97
+ show_hidden_files: Whether to show hidden files.
98
+ """
99
+ super().__init__()
100
+
101
+ self.path = Path(directory).expanduser()
102
+ if upper_limit is None:
103
+ self.upper_limit = None
104
+ elif upper_limit is ...:
105
+ self.upper_limit = Path(directory).expanduser()
106
+ else:
107
+ self.upper_limit = Path(upper_limit).expanduser()
108
+ self.show_hidden_files = show_hidden_files
109
+
110
+ with self, ui.card():
111
+ self.add_drives_toggle()
112
+ self.grid = (
113
+ ui.aggrid(
114
+ {
115
+ "columnDefs": [{"field": "name", "headerName": "File"}],
116
+ "rowSelection": "multiple" if multiple else "single",
117
+ },
118
+ html_columns=[0],
119
+ )
120
+ .classes("w-96")
121
+ .on("cellDoubleClicked", self.handle_double_click)
122
+ )
123
+ with ui.row().classes("w-full justify-end"):
124
+ ui.button("Cancel", on_click=self.close).props("outline").mark("BUTTON_CANCEL")
125
+ ui.button("Ok", on_click=self._handle_ok).mark("BUTTON_OK")
126
+ self.update_grid()
127
+
128
+ def add_drives_toggle(self) -> None:
129
+ if platform.system() == "Windows":
130
+ import win32api # noqa: PLC0415
131
+
132
+ drives = win32api.GetLogicalDriveStrings().split("\000")[:-1]
133
+ self.drives_toggle = ui.toggle(drives, value=drives[0], on_change=self.update_drive)
134
+
135
+ def update_drive(self) -> None:
136
+ self.path = Path(self.drives_toggle.value).expanduser()
137
+ self.update_grid()
138
+
139
+ def update_grid(self) -> None:
140
+ paths = list(self.path.glob("*"))
141
+ if not self.show_hidden_files:
142
+ paths = [p for p in paths if not p.name.startswith(".")]
143
+ paths.sort(key=lambda p: p.name.lower())
144
+ paths.sort(key=lambda p: not p.is_dir())
145
+
146
+ self.grid.options["rowData"] = [
147
+ {
148
+ "name": f"📁 <strong>{p.name}</strong>" if p.is_dir() else p.name,
149
+ "path": str(p),
150
+ }
151
+ for p in paths
152
+ ]
153
+ if (self.upper_limit is None and self.path != self.path.parent) or (
154
+ self.upper_limit is not None and self.path != self.upper_limit
155
+ ):
156
+ self.grid.options["rowData"].insert(
157
+ 0,
158
+ {
159
+ "name": "📁 <strong>..</strong>",
160
+ "path": str(self.path.parent),
161
+ },
162
+ )
163
+ self.grid.update()
164
+
165
+ def handle_double_click(self, e: events.GenericEventArguments) -> None:
166
+ self.path = Path(e.args["data"]["path"])
167
+ if self.path.is_dir():
168
+ self.update_grid()
169
+ else:
170
+ self.submit([str(self.path)])
171
+
172
+ async def _handle_ok(self) -> None:
173
+ rows = await self.grid.get_selected_rows()
174
+ self.submit([r["path"] for r in rows])
@@ -0,0 +1,61 @@
1
+ """System service."""
2
+
3
+ from collections.abc import Callable
4
+
5
+ import marimo
6
+ from fastapi import APIRouter, FastAPI
7
+
8
+ from ..constants import NOTEBOOK_APP, NOTEBOOK_FOLDER # noqa: TID252
9
+ from ._health import Health
10
+ from ._log import get_logger
11
+
12
+ logger = get_logger(__name__)
13
+
14
+
15
+ def register_health_endpoint(router: APIRouter) -> Callable[..., Health]:
16
+ """Register health endpoint to the given router.
17
+
18
+ Args:
19
+ router: The router to register the health endpoint to.
20
+
21
+ Returns:
22
+ Callable[..., Health]: The health endpoint function.
23
+ """
24
+
25
+ @router.get("/healthz")
26
+ def health_endpoint() -> Health:
27
+ """Determine health of the app.
28
+
29
+ Returns:
30
+ Health: Health.
31
+ """
32
+ return Health(status=Health.Code.UP)
33
+
34
+ return health_endpoint
35
+
36
+
37
+ def create_marimo_app() -> FastAPI:
38
+ """Create a FastAPI app with marimo notebook server.
39
+
40
+ Returns:
41
+ FastAPI: FastAPI app with marimo notebook server.
42
+
43
+ Raises:
44
+ ValueError: If the notebook directory does not exist.
45
+ """
46
+ server = marimo.create_asgi_app(include_code=True)
47
+ if not NOTEBOOK_FOLDER.is_dir():
48
+ logger.critical(
49
+ "Directory %s does not exist. Please create the directory and add your notebooks.",
50
+ NOTEBOOK_FOLDER,
51
+ )
52
+ message = f"Directory {NOTEBOOK_FOLDER} does not exist. Please create and add your notebooks."
53
+ raise ValueError(message)
54
+ server = server.with_app(path="/", root=str(NOTEBOOK_APP))
55
+ # .with_dynamic_directory(path="/dashboard", directory=str(self._settings.directory))
56
+ app = FastAPI()
57
+ router = APIRouter(tags=["marimo"])
58
+ register_health_endpoint(router)
59
+ app.include_router(router)
60
+ app.mount("/", server.build())
61
+ return app
@@ -1,22 +0,0 @@
1
- """CLI (Command Line Interface) of OE Python Template."""
2
-
3
- import sys
4
-
5
- import typer
6
-
7
- from .constants import MODULES_TO_INSTRUMENT
8
- from .utils import __version__, boot, console, get_logger, prepare_cli
9
-
10
- boot(MODULES_TO_INSTRUMENT)
11
- logger = get_logger(__name__)
12
-
13
- cli = typer.Typer(help="Command Line Interface of ")
14
- prepare_cli(cli, f"🧠 OE Python Template v{__version__} - built with love in Berlin 🐻")
15
-
16
- if __name__ == "__main__": # pragma: no cover
17
- try:
18
- cli()
19
- except Exception as e: # noqa: BLE001
20
- logger.critical("Fatal error occurred: %s", e)
21
- console.print(f"Fatal error occurred: {e}", style="error")
22
- sys.exit(1)
@@ -1,7 +0,0 @@
1
- """Constants for the OE Python Template."""
2
-
3
- API_VERSIONS = {
4
- "v1": "1.0.0",
5
- "v2": "2.0.0",
6
- }
7
- MODULES_TO_INSTRUMENT = ["oe_python_template.hello"]