oe-python-template-example 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl

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 (38) hide show
  1. oe_python_template_example/__init__.py +4 -18
  2. oe_python_template_example/api.py +42 -148
  3. oe_python_template_example/cli.py +13 -141
  4. oe_python_template_example/constants.py +6 -9
  5. oe_python_template_example/hello/__init__.py +17 -0
  6. oe_python_template_example/hello/_api.py +94 -0
  7. oe_python_template_example/hello/_cli.py +47 -0
  8. oe_python_template_example/hello/_constants.py +4 -0
  9. oe_python_template_example/hello/_models.py +28 -0
  10. oe_python_template_example/hello/_service.py +96 -0
  11. oe_python_template_example/{settings.py → hello/_settings.py} +6 -4
  12. oe_python_template_example/system/__init__.py +19 -0
  13. oe_python_template_example/system/_api.py +116 -0
  14. oe_python_template_example/system/_cli.py +165 -0
  15. oe_python_template_example/system/_service.py +182 -0
  16. oe_python_template_example/system/_settings.py +31 -0
  17. oe_python_template_example/utils/__init__.py +59 -0
  18. oe_python_template_example/utils/_api.py +18 -0
  19. oe_python_template_example/utils/_cli.py +68 -0
  20. oe_python_template_example/utils/_console.py +14 -0
  21. oe_python_template_example/utils/_constants.py +48 -0
  22. oe_python_template_example/utils/_di.py +70 -0
  23. oe_python_template_example/utils/_health.py +107 -0
  24. oe_python_template_example/utils/_log.py +122 -0
  25. oe_python_template_example/utils/_logfire.py +68 -0
  26. oe_python_template_example/utils/_process.py +41 -0
  27. oe_python_template_example/utils/_sentry.py +97 -0
  28. oe_python_template_example/utils/_service.py +39 -0
  29. oe_python_template_example/utils/_settings.py +80 -0
  30. oe_python_template_example/utils/boot.py +86 -0
  31. {oe_python_template_example-0.3.4.dist-info → oe_python_template_example-0.3.6.dist-info}/METADATA +77 -51
  32. oe_python_template_example-0.3.6.dist-info/RECORD +35 -0
  33. oe_python_template_example/models.py +0 -44
  34. oe_python_template_example/service.py +0 -68
  35. oe_python_template_example-0.3.4.dist-info/RECORD +0 -12
  36. {oe_python_template_example-0.3.4.dist-info → oe_python_template_example-0.3.6.dist-info}/WHEEL +0 -0
  37. {oe_python_template_example-0.3.4.dist-info → oe_python_template_example-0.3.6.dist-info}/entry_points.txt +0 -0
  38. {oe_python_template_example-0.3.4.dist-info → oe_python_template_example-0.3.6.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,97 @@
1
+ """Sentry integration for application monitoring."""
2
+
3
+ from typing import Annotated
4
+
5
+ import sentry_sdk
6
+ from pydantic import Field, PlainSerializer, SecretStr
7
+ from pydantic_settings import SettingsConfigDict
8
+ from sentry_sdk.integrations.typer import TyperIntegration
9
+
10
+ from ._constants import __env__, __env_file__, __project_name__, __version__
11
+ from ._settings import OpaqueSettings, load_settings
12
+
13
+
14
+ class SentrySettings(OpaqueSettings):
15
+ """Configuration settings for Sentry integration."""
16
+
17
+ model_config = SettingsConfigDict(
18
+ env_prefix=f"{__project_name__.upper()}_SENTRY_",
19
+ env_file=__env_file__,
20
+ env_file_encoding="utf-8",
21
+ extra="ignore",
22
+ )
23
+
24
+ dsn: Annotated[
25
+ SecretStr | None,
26
+ PlainSerializer(func=OpaqueSettings.serialize_sensitive_info, return_type=str, when_used="always"),
27
+ Field(description="Sentry DSN", examples=["https://SECRET@SECRET.ingest.de.sentry.io/SECRET"], default=None),
28
+ ]
29
+
30
+ debug: Annotated[
31
+ bool,
32
+ Field(description="Debug (https://docs.sentry.io/platforms/python/configuration/options/)", default=False),
33
+ ]
34
+
35
+ send_default_pii: Annotated[
36
+ bool,
37
+ Field(
38
+ description="Send default personal identifiable information (https://docs.sentry.io/platforms/python/configuration/options/)",
39
+ default=False,
40
+ ),
41
+ ]
42
+
43
+ max_breadcrumbs: Annotated[
44
+ int,
45
+ Field(
46
+ description="Max breadcrumbs (https://docs.sentry.io/platforms/python/configuration/options/#max_breadcrumbs)",
47
+ default=5.0,
48
+ ),
49
+ ]
50
+ sample_rate: Annotated[
51
+ float,
52
+ Field(
53
+ description="Sample Rate (https://docs.sentry.io/platforms/python/configuration/sampling/#sampling-error-events)",
54
+ default=1.0,
55
+ ),
56
+ ]
57
+ traces_sample_rate: Annotated[
58
+ float,
59
+ Field(
60
+ description="Traces Sample Rate (https://docs.sentry.io/platforms/python/configuration/sampling/#configuring-the-transaction-sample-rate)",
61
+ default=1.0,
62
+ ),
63
+ ]
64
+ profiles_sample_rate: Annotated[
65
+ float,
66
+ Field(
67
+ description="Traces Sample Rate (https://docs.sentry.io/platforms/python/tracing/#configure)",
68
+ default=1.0,
69
+ ),
70
+ ]
71
+
72
+
73
+ def sentry_initialize() -> bool:
74
+ """Initialize Sentry integration.
75
+
76
+ Returns:
77
+ bool: True if initialized successfully, False otherwise
78
+ """
79
+ settings = load_settings(SentrySettings)
80
+
81
+ if settings.dsn is None:
82
+ return False
83
+
84
+ sentry_sdk.init(
85
+ release=f"{__project_name__}@{__version__}", # https://docs.sentry.io/platforms/python/configuration/releases/,
86
+ environment=__env__,
87
+ dsn=settings.dsn.get_secret_value(),
88
+ max_breadcrumbs=settings.max_breadcrumbs,
89
+ debug=settings.debug,
90
+ send_default_pii=settings.send_default_pii,
91
+ sample_rate=settings.sample_rate,
92
+ traces_sample_rate=settings.traces_sample_rate,
93
+ profiles_sample_rate=settings.profiles_sample_rate,
94
+ integrations=[TyperIntegration()],
95
+ )
96
+
97
+ return True
@@ -0,0 +1,39 @@
1
+ """Base class for services."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, TypeVar
5
+
6
+ from pydantic_settings import BaseSettings
7
+
8
+ from ._health import Health
9
+ from ._settings import load_settings
10
+
11
+ T = TypeVar("T", bound=BaseSettings)
12
+
13
+
14
+ class BaseService(ABC):
15
+ """Base class for services."""
16
+
17
+ _settings: BaseSettings
18
+
19
+ def __init__(self, settings_class: type[T] | None = None) -> None:
20
+ """
21
+ Initialize service with optional settings.
22
+
23
+ Args:
24
+ settings_class: Optional settings class to load configuration.
25
+ """
26
+ if settings_class is not None:
27
+ self._settings = load_settings(settings_class)
28
+
29
+ def key(self) -> str:
30
+ """Return the module name of the instance."""
31
+ return self.__module__.split(".")[-2]
32
+
33
+ @abstractmethod
34
+ def health(self) -> Health:
35
+ """Get health of this service. Override in subclass."""
36
+
37
+ @abstractmethod
38
+ def info(self) -> dict[str, Any]:
39
+ """Get info of this service. Override in subclass."""
@@ -0,0 +1,80 @@
1
+ """Utilities around Pydantic settings."""
2
+
3
+ import json
4
+ import logging
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import TypeVar
8
+
9
+ from pydantic import FieldSerializationInfo, SecretStr, ValidationError
10
+ from pydantic_settings import BaseSettings
11
+ from rich.panel import Panel
12
+ from rich.text import Text
13
+
14
+ from ._console import console
15
+
16
+ T = TypeVar("T", bound=BaseSettings)
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ UNHIDE_SENSITIVE_INFO = "unhide_sensitive_info"
21
+
22
+
23
+ class OpaqueSettings(BaseSettings):
24
+ @staticmethod
25
+ def serialize_sensitive_info(input_value: SecretStr, info: FieldSerializationInfo) -> str | None:
26
+ if not input_value:
27
+ return None
28
+ if info.context.get(UNHIDE_SENSITIVE_INFO, False): # type: ignore
29
+ return input_value.get_secret_value()
30
+ return str(input_value)
31
+
32
+
33
+ def load_settings(settings_class: type[T]) -> T:
34
+ """
35
+ Load settings with error handling and nice formatting.
36
+
37
+ Args:
38
+ settings_class: The Pydantic settings class to instantiate
39
+
40
+ Returns:
41
+ (T): Instance of the settings class
42
+
43
+ Raises:
44
+ SystemExit: If settings validation fails
45
+ """
46
+ try:
47
+ return settings_class()
48
+ except ValidationError as e:
49
+ errors = json.loads(e.json())
50
+ text = Text()
51
+ text.append(
52
+ "Validation error(s): \n\n",
53
+ style="debug",
54
+ )
55
+
56
+ prefix = settings_class.model_config.get("env_prefix", "")
57
+ for error in errors:
58
+ env_var = f"{prefix}{error['loc'][0]}".upper()
59
+ logger.fatal(f"Configuration invalid! {env_var}: {error['msg']}")
60
+ text.append(f"• {env_var}", style="yellow bold")
61
+ text.append(f": {error['msg']}\n")
62
+
63
+ text.append(
64
+ "\nCheck settings defined in the process environment and in file ",
65
+ style="info",
66
+ )
67
+ env_file = str(settings_class.model_config.get("env_file", ".env") or ".env")
68
+ text.append(
69
+ str(Path(__file__).parent.parent.parent.parent / env_file),
70
+ style="bold blue underline",
71
+ )
72
+
73
+ console.print(
74
+ Panel(
75
+ text,
76
+ title="Configuration invalid!",
77
+ border_style="error",
78
+ ),
79
+ )
80
+ sys.exit(78)
@@ -0,0 +1,86 @@
1
+ """Boot sequence."""
2
+
3
+ import os
4
+ import sys
5
+
6
+ from ._log import logging_initialize
7
+ from ._logfire import logfire_initialize
8
+ from ._sentry import sentry_initialize
9
+
10
+ _boot_called = False
11
+
12
+
13
+ def boot(modules_to_instrument: list[str]) -> None:
14
+ """Boot the application.
15
+
16
+ Args:
17
+ modules_to_instrument (list): List of modules to be instrumented.
18
+ repository_url (str): URL of the repository.
19
+ repository_root_path (str): The root path of the repository. Default is the root path.
20
+ """
21
+ global _boot_called # noqa: PLW0603
22
+ if _boot_called:
23
+ return
24
+ _boot_called = True
25
+ sentry_initialize()
26
+ log_to_logfire = logfire_initialize(modules_to_instrument)
27
+ logging_initialize(log_to_logfire)
28
+ _amend_library_path()
29
+ _parse_env_args()
30
+ _log_boot_message()
31
+
32
+
33
+ from ._constants import __project_name__, __version__ # noqa: E402
34
+ from ._log import get_logger # noqa: E402
35
+ from ._process import get_process_info # noqa: E402
36
+
37
+
38
+ def _parse_env_args() -> None:
39
+ """Parse --env arguments from command line and add to environment if prefix matches.
40
+
41
+ - Last but not least removes those args so typer does not complain about them.
42
+ """
43
+ i = 1 # Start after script name
44
+ to_remove = []
45
+ prefix = f"{__project_name__.upper()}_"
46
+
47
+ while i < len(sys.argv):
48
+ current_arg = sys.argv[i]
49
+
50
+ # Handle "--env KEY=VALUE" or "-e KEY=VALUE" format (two separate arguments)
51
+ if (current_arg in {"--env", "-e"}) and i + 1 < len(sys.argv):
52
+ key_value = sys.argv[i + 1]
53
+ if "=" in key_value:
54
+ key, value = key_value.split("=", 1)
55
+ if key.startswith(prefix):
56
+ os.environ[key] = value.strip("\"'")
57
+ to_remove.extend([i, i + 1])
58
+ i += 2
59
+ continue
60
+
61
+ i += 1
62
+
63
+ # Remove processed arguments from sys.argv in reverse order
64
+ for index in sorted(to_remove, reverse=True):
65
+ del sys.argv[index]
66
+
67
+
68
+ def _amend_library_path() -> None:
69
+ """Patch environment variables before any other imports."""
70
+ if "DYLD_FALLBACK_LIBRARY_PATH" not in os.environ:
71
+ os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = f"{os.getenv('HOMEBREW_PREFIX', '/opt/homebrew')}/lib/"
72
+
73
+
74
+ def _log_boot_message() -> None:
75
+ """Log boot message with version and process information."""
76
+ logger = get_logger(__name__)
77
+ process_info = get_process_info()
78
+ logger.info(
79
+ "⭐ Booting %s v%s (project root %s, pid %s), parent '%s' (pid %s)",
80
+ __project_name__,
81
+ __version__,
82
+ process_info.project_root,
83
+ process_info.pid,
84
+ process_info.parent.name,
85
+ process_info.parent.pid,
86
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template-example
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: 🧠 Example project scaffolded and kept up to date with OE Python Template (oe-python-template).
5
5
  Project-URL: Homepage, https://oe-python-template-example.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://oe-python-template-example.readthedocs.io/en/latest/
@@ -49,13 +49,24 @@ Classifier: Programming Language :: Python :: 3.13
49
49
  Classifier: Typing :: Typed
50
50
  Requires-Python: <4.0,>=3.11
51
51
  Requires-Dist: fastapi[all,standard]>=0.115.12
52
+ Requires-Dist: logfire[system-metrics]>=3.13.1
53
+ Requires-Dist: opentelemetry-instrumentation-fastapi>=0.53b0
54
+ Requires-Dist: opentelemetry-instrumentation-httpx>=0.53b0
55
+ Requires-Dist: opentelemetry-instrumentation-jinja2>=0.53b0
56
+ Requires-Dist: opentelemetry-instrumentation-requests>=0.53b0
57
+ Requires-Dist: opentelemetry-instrumentation-sqlite3>=0.53b0
58
+ Requires-Dist: opentelemetry-instrumentation-tornado>=0.53b0
59
+ Requires-Dist: opentelemetry-instrumentation-urllib3>=0.53b0
60
+ Requires-Dist: opentelemetry-instrumentation-urllib>=0.53b0
61
+ Requires-Dist: psutil>=7.0.0
52
62
  Requires-Dist: pydantic-settings>=2.8.1
53
- Requires-Dist: pydantic>=2.11.1
63
+ Requires-Dist: pydantic>=2.11.3
64
+ Requires-Dist: sentry-sdk>=2.25.1
54
65
  Requires-Dist: typer>=0.15.1
55
66
  Provides-Extra: examples
56
67
  Requires-Dist: jinja2>=3.1.6; extra == 'examples'
57
68
  Requires-Dist: jupyter>=1.1.1; extra == 'examples'
58
- Requires-Dist: marimo>=0.12.2; extra == 'examples'
69
+ Requires-Dist: marimo>=0.12.8; extra == 'examples'
59
70
  Requires-Dist: streamlit>=1.44.1; extra == 'examples'
60
71
  Description-Content-Type: text/markdown
61
72
 
@@ -89,6 +100,8 @@ Description-Content-Type: text/markdown
89
100
  [![Copier](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/copier-org/copier/master/img/badge/badge-grayscale-inverted-border-orange.json)](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template)
90
101
  [![Open in Dev Containers](https://img.shields.io/static/v1?label=Dev%20Containers&message=Open&color=blue&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE3IDE2VjdsLTYgNU0yIDlWOGwxLTFoMWw0IDMgOC04aDFsNCAyIDEgMXYxNGwtMSAxLTQgMmgtMWwtOC04LTQgM0gzbC0xLTF2LTFsMy0zIi8+PC9zdmc+)](https://vscode.dev/redirect?url=vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example)
91
102
  [![Open in GitHub Codespaces](https://img.shields.io/static/v1?label=GitHub%20Codespaces&message=Open&color=blue&logo=github)](https://github.com/codespaces/new/helmut-hoffer-von-ankershoffen/oe-python-template-example)
103
+ [![Vercel Deploy](https://deploy-badge.vercel.app/vercel/oe-python-template-example?root=api%2Fv1%2Fhealthz)](https://oe-python-template-example.vercel.app/api/v1/hello/world)
104
+ [![Better Stack Badge](https://uptime.betterstack.com/status-badges/v1/monitor/1vzoq.svg)](https://helmut-hoffer-von-ankershoffen.betteruptime.com/)
92
105
 
93
106
  <!---
94
107
  [![ghcr.io - Version](https://ghcr-badge.egpl.dev/helmut-hoffer-von-ankershoffen/oe-python-template-example/tags?color=%2344cc11&ignore=0.0%2C0%2Clatest&n=3&label=ghcr.io&trim=)](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/pkgs/container/oe-python-template-example)
@@ -127,33 +140,40 @@ Projects generated with this template come with a comprehensive development tool
127
140
  8. CI/CD pipeline can be run locally with [act](https://github.com/nektos/act)
128
141
  9. Code quality and security checks with [SonarQube](https://www.sonarsource.com/products/sonarcloud) and [GitHub CodeQL](https://codeql.github.com/)
129
142
  10. Dependency monitoring and vulnerability scanning with [pip-audit](https://pypi.org/project/pip-audit/), [trivy](https://trivy.dev/latest/), [Renovate](https://github.com/renovatebot/renovate), and [GitHub Dependabot](https://docs.github.com/en/code-security/getting-started/dependabot-quickstart-guide)
130
- 11. Licenses of dependencies extracted with [pip-licenses](https://pypi.org/project/pip-licenses/), matched with allow list, and published as release artifacts in CSV and JSON format for further compliance checks
131
- 12. Generation of attributions from extracted licenses
132
- 13. Software Bill of Materials (SBOM) generated in [CycloneDX](https://cyclonedx.org/) and [SPDX](https://spdx.dev/) formats with [cyclonedx-python](https://github.com/CycloneDX/cyclonedx-python) resp. [trivy](https://trivy.dev/latest/), published as release artifacts
133
- 14. Version and release management with [bump-my-version](https://callowayproject.github.io/bump-my-version/)
134
- 15. Changelog and release notes generated with [git-cliff](https://git-cliff.org/)
135
- 16. Documentation generated with [Sphinx](https://www.sphinx-doc.org/en/master/) including reference documentation for the library, CLI, and API
136
- 17. Documentation published to [Read The Docs](https://readthedocs.org/) including generation of PDF and single page HTML versions
137
- 18. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
138
- 19. Python package published to [PyPI](https://pypi.org/)
139
- 20. 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)
140
- 21. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
141
- 22. Settings for use with [VSCode](https://code.visualstudio.com/)
142
- 23. Settings and custom instructions for use with [GitHub Copilot](https://docs.github.com/en/copilot/customizing-copilot/adding-repository-custom-instructions-for-github-copilot)
143
+ 11. Error monitoring and profiling with [Sentry](https://sentry.io/) (optional)
144
+ 12. Logging and metrics with [Logfire](https://logfire.dev/) (optional)
145
+ 13. Prepared for uptime monitoring with [betterstack](https://betterstack.com/) or alternatives
146
+ 13. Licenses of dependencies extracted with [pip-licenses](https://pypi.org/project/pip-licenses/), matched with allow list, and published as release artifacts in CSV and JSON format for further compliance checks
147
+ 14. Generation of attributions from extracted licenses
148
+ 15. Software Bill of Materials (SBOM) generated in [CycloneDX](https://cyclonedx.org/) and [SPDX](https://spdx.dev/) formats with [cyclonedx-python](https://github.com/CycloneDX/cyclonedx-python) resp. [trivy](https://trivy.dev/latest/), published as release artifacts
149
+ 16. Version and release management with [bump-my-version](https://callowayproject.github.io/bump-my-version/)
150
+ 17. Changelog and release notes generated with [git-cliff](https://git-cliff.org/)
151
+ 18. Documentation generated with [Sphinx](https://www.sphinx-doc.org/en/master/) including reference documentation for the library, CLI, and API
152
+ 19. Documentation published to [Read The Docs](https://readthedocs.org/) including generation of PDF and single page HTML versions
153
+ 20. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
154
+ 21. Python package published to [PyPI](https://pypi.org/)
155
+ 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)
156
+ 23. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
157
+ 24. Settings for use with [VSCode](https://code.visualstudio.com/)
158
+ 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)
159
+ 26. API deployed as serverless function to [Vercel](https://vercel.com/) (optional)
143
160
 
144
161
  ### Application Features
145
162
 
146
- Beyond development tooling, projects generated with this template include the code, documentation, and configuration of a fully functioning demo application and service. This reference implementation serves as a starting point for your own business logic with modern patterns and practices already in place:
147
-
148
- 1. Service architecture suitable for use as shared library
149
- 2. Validation with [pydantic](https://docs.pydantic.dev/)
150
- 3. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
151
- 4. Versioned Web API with [FastAPI](https://fastapi.tiangolo.com/)
152
- 5. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
153
- 6. Simple Web UI with [Streamlit](https://streamlit.io/)
154
- 7. 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/)
155
- 8. Documentation including badges, setup instructions, contribution guide and security policy
156
- 9. Preparation to deploy API as serverless function to Vercel
163
+ Beyond development tooling, projects generated with this template include the code, documentation, and configuration of a fully functioning application and service. This reference implementation serves as a starting point for your own business logic with modern patterns and enterprise practices already in place:
164
+
165
+ 1. Usable as library with "Hello" module exposing a simple service
166
+ 2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
167
+ 3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
168
+ 4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
169
+ 5. Simple Web UI with [Streamlit](https://streamlit.io/)
170
+ 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/)
171
+ 7. Validation and settings management with [pydantic](https://docs.pydantic.dev/)
172
+ 8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
173
+ 9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
174
+ 10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
175
+ 11. Modular architecture including auto-registration of services, CLI commands and API routes exposed by modules
176
+ 12. Documentation including dynamic badges, setup instructions, contribution guide and security policy
157
177
 
158
178
  Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
159
179
 
@@ -276,7 +296,7 @@ The following examples run from source - clone this repository using
276
296
  from dotenv import load_dotenv
277
297
  from rich.console import Console
278
298
 
279
- from oe_python_template_example import Service
299
+ from oe_python_template_example.hello import Service
280
300
 
281
301
  console = Console()
282
302
 
@@ -362,13 +382,15 @@ uvx oe-python-template-example --help
362
382
  Execute commands:
363
383
 
364
384
  ```shell
365
- uvx oe-python-template-example hello-world
366
- uvx oe-python-template-example echo --help
367
- uvx oe-python-template-example echo "Lorem"
368
- uvx oe-python-template-example echo "Lorem" --json
369
- uvx oe-python-template-example openapi
370
- uvx oe-python-template-example openapi --output-format=json
371
- uvx oe-python-template-example serve
385
+ uvx oe-python-template-example hello world
386
+ uvx oe-python-template-example hello echo --help
387
+ uvx oe-python-template-example hello echo "Lorem"
388
+ uvx oe-python-template-example hello echo "Lorem" --json
389
+ uvx oe-python-template-example system info
390
+ uvx oe-python-template-example system health
391
+ uvx oe-python-template-example system openapi
392
+ uvx oe-python-template-example system openapi --output-format=json
393
+ uvx oe-python-template-example system serve
372
394
  ```
373
395
 
374
396
  See the [reference documentation of the CLI](https://oe-python-template-example.readthedocs.io/en/latest/cli_reference.html) for detailed documentation of all CLI commands and options.
@@ -391,19 +413,21 @@ You can as well run the CLI within Docker.
391
413
 
392
414
  ```shell
393
415
  docker run helmuthva/oe-python-template-example --help
394
- docker run helmuthva/oe-python-template-example hello-world
395
- docker run helmuthva/oe-python-template-example echo --help
396
- docker run helmuthva/oe-python-template-example echo "Lorem"
397
- docker run helmuthva/oe-python-template-example echo "Lorem" --json
398
- docker run helmuthva/oe-python-template-example openapi
399
- docker run helmuthva/oe-python-template-example openapi --output-format=json
400
- docker run helmuthva/oe-python-template-example serve
416
+ docker run helmuthva/oe-python-template-example hello world
417
+ docker run helmuthva/oe-python-template-example hello echo --help
418
+ docker run helmuthva/oe-python-template-example hello echo "Lorem"
419
+ docker run helmuthva/oe-python-template-example hello echo "Lorem" --json
420
+ docker run helmuthva/oe-python-template-example system info
421
+ docker run helmuthva/oe-python-template-example system health
422
+ docker run helmuthva/oe-python-template-example system openapi
423
+ docker run helmuthva/oe-python-template-example system openapi --output-format=json
424
+ docker run helmuthva/oe-python-template-example system serve
401
425
  ```
402
426
 
403
427
  Execute command:
404
428
 
405
429
  ```shell
406
- docker run --env THE_VAR=MY_VALUE helmuthva/oe-python-template-example echo "Lorem Ipsum"
430
+ docker run --env THE_VAR=MY_VALUE helmuthva/oe-python-template-example hello echo "Lorem Ipsum"
407
431
  ```
408
432
 
409
433
  Or use docker compose
@@ -412,12 +436,14 @@ The .env is passed through from the host to the Docker container.
412
436
 
413
437
  ```shell
414
438
  docker compose run --remove-orphans oe-python-template-example --help
415
- docker compose run --remove-orphans oe-python-template-example hello-world
416
- docker compose run --remove-orphans oe-python-template-example echo --help
417
- docker compose run --remove-orphans oe-python-template-example echo "Lorem"
418
- docker compose run --remove-orphans oe-python-template-example echo "Lorem" --json
419
- docker compose run --remove-orphans oe-python-template-example openapi
420
- docker compose run --remove-orphans oe-python-template-example openapi --output-format=json
439
+ docker compose run --remove-orphans oe-python-template-example hello world
440
+ docker compose run --remove-orphans oe-python-template-example hello echo --help
441
+ docker compose run --remove-orphans oe-python-template-example hello echo "Lorem"
442
+ docker compose run --remove-orphans oe-python-template-example hello echo "Lorem" --json
443
+ docker compose run --remove-orphans oe-python-template-example system info
444
+ docker compose run --remove-orphans oe-python-template-example system health
445
+ docker compose run --remove-orphans oe-python-template-example system openapi
446
+ docker compose run --remove-orphans oe-python-template-example system openapi --output-format=json
421
447
  echo "Running OE Python Template Example's API container as a daemon ..."
422
448
  docker compose up -d
423
449
  echo "Waiting for the API server to start ..."
@@ -426,7 +452,7 @@ echo "Checking health of v1 API ..."
426
452
  curl http://127.0.0.1:8000/api/v1/healthz
427
453
  echo ""
428
454
  echo "Saying hello world with v1 API ..."
429
- curl http://127.0.0.1:8000/api/v1/hello-world
455
+ curl http://127.0.0.1:8000/api/v1/hello/world
430
456
  echo ""
431
457
  echo "Swagger docs of v1 API ..."
432
458
  curl http://127.0.0.1:8000/api/v1/docs
@@ -435,7 +461,7 @@ echo "Checking health of v2 API ..."
435
461
  curl http://127.0.0.1:8000/api/v2/healthz
436
462
  echo ""
437
463
  echo "Saying hello world with v1 API ..."
438
- curl http://127.0.0.1:8000/api/v2/hello-world
464
+ curl http://127.0.0.1:8000/api/v2/hello/world
439
465
  echo ""
440
466
  echo "Swagger docs of v2 API ..."
441
467
  curl http://127.0.0.1:8000/api/v2/docs
@@ -0,0 +1,35 @@
1
+ oe_python_template_example/__init__.py,sha256=_Z3Xb-x95UODU66avOiwROVaouk_s0ZNB25KFnPoS40,226
2
+ oe_python_template_example/api.py,sha256=_DcnOEWe_MJTUWZroRnXeCh9RuBm_-x4edONWHoNBlE,2100
3
+ oe_python_template_example/cli.py,sha256=rNcaCyYkm5_e-ITJJLUKvJfJ_RsGXQk0dnfiFCifTq8,679
4
+ oe_python_template_example/constants.py,sha256=fm8J1LIYF9B4Ur3r5PoUdHyPSjIXWuNydHVoCbz9Jx8,171
5
+ oe_python_template_example/hello/__init__.py,sha256=7C7gIdxkydk79-4QPyVqyUHhqP3RMFShSptzE_2J9jo,289
6
+ oe_python_template_example/hello/_api.py,sha256=B4gCojmEkvh-ScKPz0rXW70r4gVvg7SX2dfbZpUd2vU,2302
7
+ oe_python_template_example/hello/_cli.py,sha256=D1RZyz8sk7wpH1a9VDx1QtsNorOr9owxK8N7SxaaMWM,1200
8
+ oe_python_template_example/hello/_constants.py,sha256=6aRleAIcdgC13TeTzI07YwjoSwqGb2g131dw8aEoM4I,109
9
+ oe_python_template_example/hello/_models.py,sha256=JtI7wGT72u23NOxFa-oeWzdyiMg7PnHL5eg22im2_yQ,574
10
+ oe_python_template_example/hello/_service.py,sha256=GPtJjGBYK1--5UPS3PcEpypHM-ka-xjDjHpDzqFHhoc,3062
11
+ oe_python_template_example/hello/_settings.py,sha256=kvXqqYCKkT42FkiszE5MOChdsTJhC0YJfZnyLKXEtbA,992
12
+ oe_python_template_example/system/__init__.py,sha256=NNgODkr7AyJjTTJiv3pys7o2z6xi1G96g0vnsxVhlI4,427
13
+ oe_python_template_example/system/_api.py,sha256=rE9Aau3IIHXdEkOBUXOwJ7SxN3cZpgtYEuojnSWfT_4,3687
14
+ oe_python_template_example/system/_cli.py,sha256=J_4upBBdbSxbONPYmOZPbuhZcKjfnPIUeZpp0L7lY-Q,4846
15
+ oe_python_template_example/system/_service.py,sha256=G8Us2ez4MF7NWdWOUBbcxaVT3dpM-QZ_dP9b_1MkjIQ,6093
16
+ oe_python_template_example/system/_settings.py,sha256=MwMAJYifJ6jGImeSh4e9shmIXmiUSuQGHXz_Ts0mSdk,901
17
+ oe_python_template_example/utils/__init__.py,sha256=rHdmSS21CtvF3AXPMSCZO17FTzxPDo-NiKZ5AjVU9b0,1449
18
+ oe_python_template_example/utils/_api.py,sha256=w3hPQK1pL2gBI4_1qNWNa2b4S_oH-8mY-ckRX0KrCWM,617
19
+ oe_python_template_example/utils/_cli.py,sha256=J_mFtXZ1gGeovGrE5i3wlokTOBfiTTKEz5magiRP7GA,2091
20
+ oe_python_template_example/utils/_console.py,sha256=u0-utcdRmVu4rabrYUyNOx8yPxLhxB3E92m22kSCwPQ,293
21
+ oe_python_template_example/utils/_constants.py,sha256=1ocbciHjhmy66uHrTw6p-fbBq_wLl1HaUSu4oTgt8mo,1737
22
+ oe_python_template_example/utils/_di.py,sha256=KdjiD4xZ_QSfbddkKWwsPJmG5YrIg6dzuBrlsd-FhxA,2189
23
+ oe_python_template_example/utils/_health.py,sha256=35QOWe2r5InrEpGtuVMym9dI5aRHS0HWf4BHBRAUIj0,4102
24
+ oe_python_template_example/utils/_log.py,sha256=ZW4gs540SdjVK-2KeheLfDY15d_3xpO5FyGn7wTXyaM,3592
25
+ oe_python_template_example/utils/_logfire.py,sha256=g2tR60XgpdRs_UwDpqkwWm6ErUBcV7lPFBJdvgfTuu0,2022
26
+ oe_python_template_example/utils/_process.py,sha256=40R0NZMqJUn0iUPERzohSUpJgU1HcJApIg1HipIxFCw,941
27
+ oe_python_template_example/utils/_sentry.py,sha256=Y4hZ-PeBOdR3iRhoXW9j0tbWsYf07460UG8OVTKH1mU,3128
28
+ oe_python_template_example/utils/_service.py,sha256=atHAejvBucKXjzhsMSdOBBFa7rRD74zcV70Pp0pl0Tg,1038
29
+ oe_python_template_example/utils/_settings.py,sha256=5K1pnp-AxMQbktREb3bXDmqgrOx_L4EJIgjPQfqH4sE,2294
30
+ oe_python_template_example/utils/boot.py,sha256=TBgmqbtIryQz0cAozYzxhYQRIldfbJ6v9R-rH6sO9mY,2696
31
+ oe_python_template_example-0.3.6.dist-info/METADATA,sha256=6uSD_IpqJvgW1nlSseTwcNKH2lfVEAqi78s3egRdIq8,33162
32
+ oe_python_template_example-0.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ oe_python_template_example-0.3.6.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
34
+ oe_python_template_example-0.3.6.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
35
+ oe_python_template_example-0.3.6.dist-info/RECORD,,
@@ -1,44 +0,0 @@
1
- """Models used throughout OE Python Template Example's codebase ."""
2
-
3
- from enum import StrEnum
4
-
5
- from pydantic import BaseModel, Field
6
-
7
- UTTERANCE_EXAMPLE = "Hello, world!"
8
- ECHO_EXAMPLE = "HELLO, WORLD!"
9
-
10
-
11
- class Utterance(BaseModel):
12
- """Model representing a text utterance."""
13
-
14
- text: str = Field(
15
- ...,
16
- min_length=1,
17
- description="The utterance to echo back",
18
- examples=[UTTERANCE_EXAMPLE],
19
- )
20
-
21
-
22
- class Echo(BaseModel):
23
- """Response model for echo endpoint."""
24
-
25
- text: str = Field(
26
- ...,
27
- min_length=1,
28
- description="The echo",
29
- examples=[ECHO_EXAMPLE],
30
- )
31
-
32
-
33
- class HealthStatus(StrEnum):
34
- """Health status enumeration."""
35
-
36
- UP = "UP"
37
- DOWN = "DOWN"
38
-
39
-
40
- class Health(BaseModel):
41
- """Health status model."""
42
-
43
- status: HealthStatus
44
- reason: str | None = None