oe-python-template-example 0.3.5__py3-none-any.whl → 0.3.7__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.
- oe_python_template_example/__init__.py +4 -18
- oe_python_template_example/api.py +42 -148
- oe_python_template_example/cli.py +13 -141
- oe_python_template_example/constants.py +6 -9
- oe_python_template_example/hello/__init__.py +17 -0
- oe_python_template_example/hello/_api.py +94 -0
- oe_python_template_example/hello/_cli.py +47 -0
- oe_python_template_example/hello/_constants.py +4 -0
- oe_python_template_example/hello/_models.py +28 -0
- oe_python_template_example/hello/_service.py +96 -0
- oe_python_template_example/{settings.py → hello/_settings.py} +6 -4
- oe_python_template_example/system/__init__.py +19 -0
- oe_python_template_example/system/_api.py +116 -0
- oe_python_template_example/system/_cli.py +165 -0
- oe_python_template_example/system/_service.py +186 -0
- oe_python_template_example/system/_settings.py +31 -0
- oe_python_template_example/utils/__init__.py +59 -0
- oe_python_template_example/utils/_api.py +18 -0
- oe_python_template_example/utils/_cli.py +68 -0
- oe_python_template_example/utils/_console.py +14 -0
- oe_python_template_example/utils/_constants.py +48 -0
- oe_python_template_example/utils/_di.py +70 -0
- oe_python_template_example/utils/_health.py +107 -0
- oe_python_template_example/utils/_log.py +122 -0
- oe_python_template_example/utils/_logfire.py +68 -0
- oe_python_template_example/utils/_process.py +41 -0
- oe_python_template_example/utils/_sentry.py +97 -0
- oe_python_template_example/utils/_service.py +39 -0
- oe_python_template_example/utils/_settings.py +80 -0
- oe_python_template_example/utils/boot.py +86 -0
- {oe_python_template_example-0.3.5.dist-info → oe_python_template_example-0.3.7.dist-info}/METADATA +78 -51
- oe_python_template_example-0.3.7.dist-info/RECORD +35 -0
- oe_python_template_example/models.py +0 -44
- oe_python_template_example/service.py +0 -68
- oe_python_template_example-0.3.5.dist-info/RECORD +0 -12
- {oe_python_template_example-0.3.5.dist-info → oe_python_template_example-0.3.7.dist-info}/WHEEL +0 -0
- {oe_python_template_example-0.3.5.dist-info → oe_python_template_example-0.3.7.dist-info}/entry_points.txt +0 -0
- {oe_python_template_example-0.3.5.dist-info → oe_python_template_example-0.3.7.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
|
+
)
|
{oe_python_template_example-0.3.5.dist-info → oe_python_template_example-0.3.7.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: oe-python-template-example
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.7
|
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,25 @@ 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.
|
63
|
+
Requires-Dist: pydantic>=2.11.3
|
64
|
+
Requires-Dist: sentry-sdk>=2.25.1
|
54
65
|
Requires-Dist: typer>=0.15.1
|
66
|
+
Requires-Dist: uptime>=3.0.1
|
55
67
|
Provides-Extra: examples
|
56
68
|
Requires-Dist: jinja2>=3.1.6; extra == 'examples'
|
57
69
|
Requires-Dist: jupyter>=1.1.1; extra == 'examples'
|
58
|
-
Requires-Dist: marimo>=0.12.
|
70
|
+
Requires-Dist: marimo>=0.12.8; extra == 'examples'
|
59
71
|
Requires-Dist: streamlit>=1.44.1; extra == 'examples'
|
60
72
|
Description-Content-Type: text/markdown
|
61
73
|
|
@@ -89,6 +101,8 @@ Description-Content-Type: text/markdown
|
|
89
101
|
[](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template)
|
90
102
|
[](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
103
|
[](https://github.com/codespaces/new/helmut-hoffer-von-ankershoffen/oe-python-template-example)
|
104
|
+
[](https://oe-python-template-example.vercel.app/api/v1/hello/world)
|
105
|
+
[](https://helmut-hoffer-von-ankershoffen.betteruptime.com/)
|
92
106
|
|
93
107
|
<!---
|
94
108
|
[](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example/pkgs/container/oe-python-template-example)
|
@@ -127,33 +141,40 @@ Projects generated with this template come with a comprehensive development tool
|
|
127
141
|
8. CI/CD pipeline can be run locally with [act](https://github.com/nektos/act)
|
128
142
|
9. Code quality and security checks with [SonarQube](https://www.sonarsource.com/products/sonarcloud) and [GitHub CodeQL](https://codeql.github.com/)
|
129
143
|
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.
|
131
|
-
12.
|
132
|
-
13.
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
144
|
+
11. Error monitoring and profiling with [Sentry](https://sentry.io/) (optional)
|
145
|
+
12. Logging and metrics with [Logfire](https://logfire.dev/) (optional)
|
146
|
+
13. Prepared for uptime monitoring with [betterstack](https://betterstack.com/) or alternatives
|
147
|
+
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
|
148
|
+
14. Generation of attributions from extracted licenses
|
149
|
+
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
|
150
|
+
16. Version and release management with [bump-my-version](https://callowayproject.github.io/bump-my-version/)
|
151
|
+
17. Changelog and release notes generated with [git-cliff](https://git-cliff.org/)
|
152
|
+
18. Documentation generated with [Sphinx](https://www.sphinx-doc.org/en/master/) including reference documentation for the library, CLI, and API
|
153
|
+
19. Documentation published to [Read The Docs](https://readthedocs.org/) including generation of PDF and single page HTML versions
|
154
|
+
20. Interactive OpenAPI specification with [Swagger](https://swagger.io/)
|
155
|
+
21. Python package published to [PyPI](https://pypi.org/)
|
156
|
+
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)
|
157
|
+
23. One-click development environments with [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) and [GitHub Codespaces](https://github.com/features/codespaces)
|
158
|
+
24. Settings for use with [VSCode](https://code.visualstudio.com/)
|
159
|
+
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)
|
160
|
+
26. API deployed as serverless function to [Vercel](https://vercel.com/) (optional)
|
143
161
|
|
144
162
|
### Application Features
|
145
163
|
|
146
|
-
Beyond development tooling, projects generated with this template include the code, documentation, and configuration of a fully functioning
|
147
|
-
|
148
|
-
1.
|
149
|
-
2.
|
150
|
-
3.
|
151
|
-
4.
|
152
|
-
5.
|
153
|
-
6.
|
154
|
-
7.
|
155
|
-
8.
|
156
|
-
9.
|
164
|
+
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:
|
165
|
+
|
166
|
+
1. Usable as library with "Hello" module exposing a simple service
|
167
|
+
2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
|
168
|
+
3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
|
169
|
+
4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
|
170
|
+
5. Simple Web UI with [Streamlit](https://streamlit.io/)
|
171
|
+
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/)
|
172
|
+
7. Validation and settings management with [pydantic](https://docs.pydantic.dev/)
|
173
|
+
8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
|
174
|
+
9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
|
175
|
+
10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
|
176
|
+
11. Modular architecture including auto-registration of services, CLI commands and API routes exposed by modules
|
177
|
+
12. Documentation including dynamic badges, setup instructions, contribution guide and security policy
|
157
178
|
|
158
179
|
Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
|
159
180
|
|
@@ -276,7 +297,7 @@ The following examples run from source - clone this repository using
|
|
276
297
|
from dotenv import load_dotenv
|
277
298
|
from rich.console import Console
|
278
299
|
|
279
|
-
from oe_python_template_example import Service
|
300
|
+
from oe_python_template_example.hello import Service
|
280
301
|
|
281
302
|
console = Console()
|
282
303
|
|
@@ -362,13 +383,15 @@ uvx oe-python-template-example --help
|
|
362
383
|
Execute commands:
|
363
384
|
|
364
385
|
```shell
|
365
|
-
uvx oe-python-template-example hello
|
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
|
370
|
-
uvx oe-python-template-example
|
371
|
-
uvx oe-python-template-example
|
386
|
+
uvx oe-python-template-example hello world
|
387
|
+
uvx oe-python-template-example hello echo --help
|
388
|
+
uvx oe-python-template-example hello echo "Lorem"
|
389
|
+
uvx oe-python-template-example hello echo "Lorem" --json
|
390
|
+
uvx oe-python-template-example system info
|
391
|
+
uvx oe-python-template-example system health
|
392
|
+
uvx oe-python-template-example system openapi
|
393
|
+
uvx oe-python-template-example system openapi --output-format=json
|
394
|
+
uvx oe-python-template-example system serve
|
372
395
|
```
|
373
396
|
|
374
397
|
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 +414,21 @@ You can as well run the CLI within Docker.
|
|
391
414
|
|
392
415
|
```shell
|
393
416
|
docker run helmuthva/oe-python-template-example --help
|
394
|
-
docker run helmuthva/oe-python-template-example hello
|
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
|
399
|
-
docker run helmuthva/oe-python-template-example
|
400
|
-
docker run helmuthva/oe-python-template-example
|
417
|
+
docker run helmuthva/oe-python-template-example hello world
|
418
|
+
docker run helmuthva/oe-python-template-example hello echo --help
|
419
|
+
docker run helmuthva/oe-python-template-example hello echo "Lorem"
|
420
|
+
docker run helmuthva/oe-python-template-example hello echo "Lorem" --json
|
421
|
+
docker run helmuthva/oe-python-template-example system info
|
422
|
+
docker run helmuthva/oe-python-template-example system health
|
423
|
+
docker run helmuthva/oe-python-template-example system openapi
|
424
|
+
docker run helmuthva/oe-python-template-example system openapi --output-format=json
|
425
|
+
docker run helmuthva/oe-python-template-example system serve
|
401
426
|
```
|
402
427
|
|
403
428
|
Execute command:
|
404
429
|
|
405
430
|
```shell
|
406
|
-
docker run --env THE_VAR=MY_VALUE helmuthva/oe-python-template-example echo "Lorem Ipsum"
|
431
|
+
docker run --env THE_VAR=MY_VALUE helmuthva/oe-python-template-example hello echo "Lorem Ipsum"
|
407
432
|
```
|
408
433
|
|
409
434
|
Or use docker compose
|
@@ -412,12 +437,14 @@ The .env is passed through from the host to the Docker container.
|
|
412
437
|
|
413
438
|
```shell
|
414
439
|
docker compose run --remove-orphans oe-python-template-example --help
|
415
|
-
docker compose run --remove-orphans oe-python-template-example hello
|
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
|
420
|
-
docker compose run --remove-orphans oe-python-template-example
|
440
|
+
docker compose run --remove-orphans oe-python-template-example hello world
|
441
|
+
docker compose run --remove-orphans oe-python-template-example hello echo --help
|
442
|
+
docker compose run --remove-orphans oe-python-template-example hello echo "Lorem"
|
443
|
+
docker compose run --remove-orphans oe-python-template-example hello echo "Lorem" --json
|
444
|
+
docker compose run --remove-orphans oe-python-template-example system info
|
445
|
+
docker compose run --remove-orphans oe-python-template-example system health
|
446
|
+
docker compose run --remove-orphans oe-python-template-example system openapi
|
447
|
+
docker compose run --remove-orphans oe-python-template-example system openapi --output-format=json
|
421
448
|
echo "Running OE Python Template Example's API container as a daemon ..."
|
422
449
|
docker compose up -d
|
423
450
|
echo "Waiting for the API server to start ..."
|
@@ -426,7 +453,7 @@ echo "Checking health of v1 API ..."
|
|
426
453
|
curl http://127.0.0.1:8000/api/v1/healthz
|
427
454
|
echo ""
|
428
455
|
echo "Saying hello world with v1 API ..."
|
429
|
-
curl http://127.0.0.1:8000/api/v1/hello
|
456
|
+
curl http://127.0.0.1:8000/api/v1/hello/world
|
430
457
|
echo ""
|
431
458
|
echo "Swagger docs of v1 API ..."
|
432
459
|
curl http://127.0.0.1:8000/api/v1/docs
|
@@ -435,7 +462,7 @@ echo "Checking health of v2 API ..."
|
|
435
462
|
curl http://127.0.0.1:8000/api/v2/healthz
|
436
463
|
echo ""
|
437
464
|
echo "Saying hello world with v1 API ..."
|
438
|
-
curl http://127.0.0.1:8000/api/v2/hello
|
465
|
+
curl http://127.0.0.1:8000/api/v2/hello/world
|
439
466
|
echo ""
|
440
467
|
echo "Swagger docs of v2 API ..."
|
441
468
|
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=e08VPguXKz2RhyhTbIw1ncSpE6zwKUr1xLM2WZC9u7k,6287
|
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.7.dist-info/METADATA,sha256=fHz4k1yEXUdTCmimGQ3ANZQ4e5kWKxxO5vZ4Gb2VqZE,33191
|
32
|
+
oe_python_template_example-0.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
33
|
+
oe_python_template_example-0.3.7.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
|
34
|
+
oe_python_template_example-0.3.7.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
|
35
|
+
oe_python_template_example-0.3.7.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
|