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.
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/.gitignore +7 -1
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/PKG-INFO +50 -28
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/README.md +43 -24
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/pyproject.toml +34 -28
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/api.py +2 -2
- oe_python_template-0.12.0/src/oe_python_template/cli.py +55 -0
- oe_python_template-0.12.0/src/oe_python_template/constants.py +13 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/__init__.py +11 -0
- oe_python_template-0.12.0/src/oe_python_template/hello/_gui.py +40 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/__init__.py +12 -1
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_cli.py +64 -23
- oe_python_template-0.12.0/src/oe_python_template/system/_gui.py +20 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/__init__.py +14 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_constants.py +27 -8
- oe_python_template-0.12.0/src/oe_python_template/utils/_gui.py +174 -0
- oe_python_template-0.12.0/src/oe_python_template/utils/_notebook.py +61 -0
- oe_python_template-0.11.2/src/oe_python_template/cli.py +0 -22
- oe_python_template-0.11.2/src/oe_python_template/constants.py +0 -7
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/LICENSE +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/__init__.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_api.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_cli.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_constants.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_models.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_service.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_settings.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_api.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_service.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_settings.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_api.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_cli.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_console.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_di.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_health.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_log.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_logfire.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_process.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_sentry.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_service.py +0 -0
- {oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_settings.py +0 -0
- {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
|
-
|
|
88
|
+
|
|
89
|
+
# Application specific
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oe-python-template
|
|
3
|
-
Version: 0.
|
|
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.
|
|
62
|
+
Requires-Dist: pydantic-settings>=2.9.1
|
|
63
63
|
Requires-Dist: pydantic>=2.11.3
|
|
64
|
-
Requires-Dist: sentry-sdk>=2.
|
|
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.
|
|
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.
|
|
152
|
-
21.
|
|
153
|
-
22.
|
|
154
|
-
23.
|
|
155
|
-
24.
|
|
156
|
-
25.
|
|
157
|
-
26.
|
|
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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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.
|
|
171
|
-
9.
|
|
172
|
-
10.
|
|
173
|
-
11.
|
|
174
|
-
12.
|
|
175
|
-
13.
|
|
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
|
|
243
|
-
uvx oe-python-template
|
|
244
|
-
uvx oe-python-template
|
|
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
|
|
258
|
-
uvx oe-python-template echo --help
|
|
259
|
-
uvx oe-python-template
|
|
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.
|
|
79
|
-
21.
|
|
80
|
-
22.
|
|
81
|
-
23.
|
|
82
|
-
24.
|
|
83
|
-
25.
|
|
84
|
-
26.
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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.
|
|
98
|
-
9.
|
|
99
|
-
10.
|
|
100
|
-
11.
|
|
101
|
-
12.
|
|
102
|
-
13.
|
|
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
|
|
170
|
-
uvx oe-python-template
|
|
171
|
-
uvx oe-python-template
|
|
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
|
|
185
|
-
uvx oe-python-template echo --help
|
|
186
|
-
uvx oe-python-template
|
|
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.
|
|
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.
|
|
77
|
-
"sentry-sdk>=2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
@@ -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")
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/__init__.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
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}"
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_cli.py
RENAMED
|
@@ -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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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")
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/__init__.py
RENAMED
|
@@ -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
|
+
]
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_constants.py
RENAMED
|
@@ -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
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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)
|
|
File without changes
|
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_api.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_cli.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_constants.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_models.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_service.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/hello/_settings.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_api.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_service.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/system/_settings.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_api.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_cli.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_console.py
RENAMED
|
File without changes
|
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_health.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_log.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_logfire.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_process.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_sentry.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_service.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/_settings.py
RENAMED
|
File without changes
|
{oe_python_template-0.11.2 → oe_python_template-0.12.0}/src/oe_python_template/utils/boot.py
RENAMED
|
File without changes
|