oe-python-template-example 0.3.7__tar.gz → 0.3.8__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_example-0.3.7 → oe_python_template_example-0.3.8}/PKG-INFO +5 -4
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/README.md +4 -3
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/pyproject.toml +2 -2
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/api.py +5 -1
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_service.py +4 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_settings.py +21 -4
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_service.py +3 -1
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/__init__.py +4 -1
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_constants.py +11 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_logfire.py +3 -2
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_sentry.py +9 -4
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_settings.py +9 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/.gitignore +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/LICENSE +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/__init__.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/cli.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/constants.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/__init__.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_api.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_cli.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_constants.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_models.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/__init__.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_api.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_cli.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_settings.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_api.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_cli.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_console.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_di.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_health.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_log.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_process.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_service.py +0 -0
- {oe_python_template_example-0.3.7 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/boot.py +0 -0
@@ -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.8
|
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/
|
@@ -163,7 +163,7 @@ Projects generated with this template come with a comprehensive development tool
|
|
163
163
|
|
164
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
165
|
|
166
|
-
1. Usable as library with "Hello" module exposing a simple service
|
166
|
+
1. Usable as library with "Hello" module exposing a simple service that can say "Hello, world!" and echo utterances.
|
167
167
|
2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
|
168
168
|
3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
|
169
169
|
4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
|
@@ -173,8 +173,9 @@ Beyond development tooling, projects generated with this template include the co
|
|
173
173
|
8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
|
174
174
|
9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
|
175
175
|
10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
|
176
|
-
11.
|
177
|
-
12.
|
176
|
+
11. Hello service demonstrates use of custom real time metrics collected via Logfire
|
177
|
+
12. Modular architecture including auto-registration of services, CLI commands and API routes exposed by modules
|
178
|
+
13. Documentation including dynamic badges, setup instructions, contribution guide and security policy
|
178
179
|
|
179
180
|
Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
|
180
181
|
|
@@ -90,7 +90,7 @@ Projects generated with this template come with a comprehensive development tool
|
|
90
90
|
|
91
91
|
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:
|
92
92
|
|
93
|
-
1. Usable as library with "Hello" module exposing a simple service
|
93
|
+
1. Usable as library with "Hello" module exposing a simple service that can say "Hello, world!" and echo utterances.
|
94
94
|
2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
|
95
95
|
3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
|
96
96
|
4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
|
@@ -100,8 +100,9 @@ Beyond development tooling, projects generated with this template include the co
|
|
100
100
|
8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
|
101
101
|
9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
|
102
102
|
10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
|
103
|
-
11.
|
104
|
-
12.
|
103
|
+
11. Hello service demonstrates use of custom real time metrics collected via Logfire
|
104
|
+
12. Modular architecture including auto-registration of services, CLI commands and API routes exposed by modules
|
105
|
+
13. Documentation including dynamic badges, setup instructions, contribution guide and security policy
|
105
106
|
|
106
107
|
Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
|
107
108
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "oe-python-template-example"
|
3
|
-
version = "0.3.
|
3
|
+
version = "0.3.8"
|
4
4
|
description = "🧠 Example project scaffolded and kept up to date with OE Python Template (oe-python-template)."
|
5
5
|
readme = "README.md"
|
6
6
|
authors = [
|
@@ -278,7 +278,7 @@ source = ["src/"]
|
|
278
278
|
|
279
279
|
|
280
280
|
[tool.bumpversion]
|
281
|
-
current_version = "0.3.
|
281
|
+
current_version = "0.3.8"
|
282
282
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
283
283
|
serialize = ["{major}.{minor}.{patch}"]
|
284
284
|
search = "{current_version}"
|
@@ -13,6 +13,7 @@ from .utils import (
|
|
13
13
|
VersionedAPIRouter,
|
14
14
|
__author_email__,
|
15
15
|
__author_name__,
|
16
|
+
__base__url__,
|
16
17
|
__documentation__url__,
|
17
18
|
__repository_url__,
|
18
19
|
locate_implementations,
|
@@ -26,6 +27,9 @@ CONTACT_EMAIL = __author_email__
|
|
26
27
|
CONTACT_URL = __repository_url__
|
27
28
|
TERMS_OF_SERVICE_URL = __documentation__url__
|
28
29
|
|
30
|
+
API_BASE_URL = __base__url__
|
31
|
+
if not API_BASE_URL:
|
32
|
+
API_BASE_URL = f"http://{UVICORN_HOST}:{UVICORN_PORT}"
|
29
33
|
|
30
34
|
app = FastAPI(
|
31
35
|
root_path="/api",
|
@@ -42,7 +46,7 @@ app = FastAPI(
|
|
42
46
|
"description": f"API version {version.lstrip('v')}, check link on the right",
|
43
47
|
"externalDocs": {
|
44
48
|
"description": "sub-docs",
|
45
|
-
"url": f"
|
49
|
+
"url": f"{API_BASE_URL}/api/{version}/docs",
|
46
50
|
},
|
47
51
|
}
|
48
52
|
for version, _ in API_VERSIONS.items()
|
@@ -5,6 +5,7 @@ import string
|
|
5
5
|
from http import HTTPStatus
|
6
6
|
from typing import Any
|
7
7
|
|
8
|
+
import logfire
|
8
9
|
import requests
|
9
10
|
|
10
11
|
from oe_python_template_example.utils import BaseService, Health
|
@@ -74,6 +75,9 @@ class Service(BaseService):
|
|
74
75
|
Returns:
|
75
76
|
str: Hello world message.
|
76
77
|
"""
|
78
|
+
messages_sent = logfire.metric_counter("hello_world_messages_sent")
|
79
|
+
messages_sent.add(1)
|
80
|
+
|
77
81
|
match self._settings.language:
|
78
82
|
case Language.GERMAN:
|
79
83
|
return HELLO_WORLD_DE_DE
|
@@ -3,10 +3,15 @@
|
|
3
3
|
from enum import StrEnum
|
4
4
|
from typing import Annotated
|
5
5
|
|
6
|
-
from pydantic import Field
|
7
|
-
from pydantic_settings import
|
6
|
+
from pydantic import BeforeValidator, Field, PlainSerializer, SecretStr
|
7
|
+
from pydantic_settings import SettingsConfigDict
|
8
8
|
|
9
|
-
from oe_python_template_example.utils import
|
9
|
+
from oe_python_template_example.utils import (
|
10
|
+
OpaqueSettings,
|
11
|
+
__env_file__,
|
12
|
+
__project_name__,
|
13
|
+
strip_to_none_before_validator,
|
14
|
+
)
|
10
15
|
|
11
16
|
|
12
17
|
class Language(StrEnum):
|
@@ -18,7 +23,7 @@ class Language(StrEnum):
|
|
18
23
|
|
19
24
|
# Settings derived from BaseSettings and exported by modules via their __init__.py are automatically registered
|
20
25
|
# by the system module e.g. for showing all settings via the system info command.
|
21
|
-
class Settings(
|
26
|
+
class Settings(OpaqueSettings):
|
22
27
|
"""Settings."""
|
23
28
|
|
24
29
|
model_config = SettingsConfigDict(
|
@@ -35,3 +40,15 @@ class Settings(BaseSettings):
|
|
35
40
|
description="Language to use for output - defaults to US english.",
|
36
41
|
),
|
37
42
|
]
|
43
|
+
|
44
|
+
token: Annotated[
|
45
|
+
SecretStr | None,
|
46
|
+
BeforeValidator(strip_to_none_before_validator), # strip and if empty set to None
|
47
|
+
PlainSerializer(
|
48
|
+
func=OpaqueSettings.serialize_sensitive_info, return_type=str, when_used="always"
|
49
|
+
), # allow to unhide sensitive info from CLI or if user presents valid token via API
|
50
|
+
Field(
|
51
|
+
description="Secret token of Hello module.",
|
52
|
+
default=None,
|
53
|
+
),
|
54
|
+
]
|
@@ -149,7 +149,9 @@ class Service(BaseService):
|
|
149
149
|
for settings_class in locate_subclasses(BaseSettings):
|
150
150
|
settings_instance = load_settings(settings_class)
|
151
151
|
env_prefix = settings_instance.model_config.get("env_prefix", "")
|
152
|
-
settings_dict =
|
152
|
+
settings_dict = json.loads(
|
153
|
+
settings_instance.model_dump_json(context={UNHIDE_SENSITIVE_INFO: not filter_secrets})
|
154
|
+
)
|
153
155
|
for key, value in settings_dict.items():
|
154
156
|
flat_key = f"{env_prefix}{key}".upper()
|
155
157
|
settings[flat_key] = value
|
@@ -6,6 +6,7 @@ from ._console import console
|
|
6
6
|
from ._constants import (
|
7
7
|
__author_email__,
|
8
8
|
__author_name__,
|
9
|
+
__base__url__,
|
9
10
|
__documentation__url__,
|
10
11
|
__env__,
|
11
12
|
__env_file__,
|
@@ -23,7 +24,7 @@ from ._logfire import LogfireSettings
|
|
23
24
|
from ._process import ProcessInfo, get_process_info
|
24
25
|
from ._sentry import SentrySettings
|
25
26
|
from ._service import BaseService
|
26
|
-
from ._settings import UNHIDE_SENSITIVE_INFO, OpaqueSettings, load_settings
|
27
|
+
from ._settings import UNHIDE_SENSITIVE_INFO, OpaqueSettings, load_settings, strip_to_none_before_validator
|
27
28
|
from .boot import boot
|
28
29
|
|
29
30
|
__all__ = [
|
@@ -39,6 +40,7 @@ __all__ = [
|
|
39
40
|
"VersionedAPIRouter",
|
40
41
|
"__author_email__",
|
41
42
|
"__author_name__",
|
43
|
+
"__base__url__",
|
42
44
|
"__documentation__url__",
|
43
45
|
"__env__",
|
44
46
|
"__env_file__",
|
@@ -56,4 +58,5 @@ __all__ = [
|
|
56
58
|
"locate_implementations",
|
57
59
|
"locate_subclasses",
|
58
60
|
"prepare_cli",
|
61
|
+
"strip_to_none_before_validator",
|
59
62
|
]
|
@@ -5,6 +5,10 @@ import sys
|
|
5
5
|
from importlib import metadata
|
6
6
|
from pathlib import Path
|
7
7
|
|
8
|
+
from dotenv import load_dotenv
|
9
|
+
|
10
|
+
load_dotenv()
|
11
|
+
|
8
12
|
__project_name__ = __name__.split(".")[0]
|
9
13
|
__project_path__ = str(Path(__file__).parent.parent.parent)
|
10
14
|
__version__ = metadata.version(__project_name__)
|
@@ -21,6 +25,13 @@ env_file_path = os.getenv(f"{__project_name__.upper()}_ENV_FILE")
|
|
21
25
|
if env_file_path:
|
22
26
|
__env_file__.insert(2, Path(env_file_path))
|
23
27
|
|
28
|
+
vercel_base_url = os.getenv("VERCEL_URL", None)
|
29
|
+
if vercel_base_url:
|
30
|
+
vercel_base_url = "https://" + vercel_base_url
|
31
|
+
__base__url__ = os.getenv(__project_name__.upper() + "_BASE_URL", None)
|
32
|
+
if not __base__url__ and vercel_base_url:
|
33
|
+
__base__url__ = vercel_base_url
|
34
|
+
|
24
35
|
|
25
36
|
def get_project_url_by_label(prefix: str) -> str:
|
26
37
|
"""Get labeled Project-URL.
|
@@ -3,11 +3,11 @@
|
|
3
3
|
from typing import Annotated
|
4
4
|
|
5
5
|
import logfire
|
6
|
-
from pydantic import Field, PlainSerializer, SecretStr
|
6
|
+
from pydantic import BeforeValidator, Field, PlainSerializer, SecretStr
|
7
7
|
from pydantic_settings import SettingsConfigDict
|
8
8
|
|
9
9
|
from ._constants import __env__, __env_file__, __project_name__, __repository_url__, __version__
|
10
|
-
from ._settings import OpaqueSettings, load_settings
|
10
|
+
from ._settings import OpaqueSettings, load_settings, strip_to_none_before_validator
|
11
11
|
|
12
12
|
|
13
13
|
class LogfireSettings(OpaqueSettings):
|
@@ -22,6 +22,7 @@ class LogfireSettings(OpaqueSettings):
|
|
22
22
|
|
23
23
|
token: Annotated[
|
24
24
|
SecretStr | None,
|
25
|
+
BeforeValidator(strip_to_none_before_validator),
|
25
26
|
PlainSerializer(func=OpaqueSettings.serialize_sensitive_info, return_type=str, when_used="always"),
|
26
27
|
Field(description="Logfire token. Leave empty to disable logfire.", examples=["YOUR_TOKEN"], default=None),
|
27
28
|
]
|
@@ -3,12 +3,12 @@
|
|
3
3
|
from typing import Annotated
|
4
4
|
|
5
5
|
import sentry_sdk
|
6
|
-
from pydantic import Field, PlainSerializer, SecretStr
|
6
|
+
from pydantic import BeforeValidator, Field, PlainSerializer, SecretStr
|
7
7
|
from pydantic_settings import SettingsConfigDict
|
8
8
|
from sentry_sdk.integrations.typer import TyperIntegration
|
9
9
|
|
10
10
|
from ._constants import __env__, __env_file__, __project_name__, __version__
|
11
|
-
from ._settings import OpaqueSettings, load_settings
|
11
|
+
from ._settings import OpaqueSettings, load_settings, strip_to_none_before_validator
|
12
12
|
|
13
13
|
|
14
14
|
class SentrySettings(OpaqueSettings):
|
@@ -23,6 +23,7 @@ class SentrySettings(OpaqueSettings):
|
|
23
23
|
|
24
24
|
dsn: Annotated[
|
25
25
|
SecretStr | None,
|
26
|
+
BeforeValidator(strip_to_none_before_validator),
|
26
27
|
PlainSerializer(func=OpaqueSettings.serialize_sensitive_info, return_type=str, when_used="always"),
|
27
28
|
Field(description="Sentry DSN", examples=["https://SECRET@SECRET.ingest.de.sentry.io/SECRET"], default=None),
|
28
29
|
]
|
@@ -44,12 +45,14 @@ class SentrySettings(OpaqueSettings):
|
|
44
45
|
int,
|
45
46
|
Field(
|
46
47
|
description="Max breadcrumbs (https://docs.sentry.io/platforms/python/configuration/options/#max_breadcrumbs)",
|
47
|
-
|
48
|
+
ge=0,
|
49
|
+
default=50,
|
48
50
|
),
|
49
51
|
]
|
50
52
|
sample_rate: Annotated[
|
51
53
|
float,
|
52
54
|
Field(
|
55
|
+
ge=0.0,
|
53
56
|
description="Sample Rate (https://docs.sentry.io/platforms/python/configuration/sampling/#sampling-error-events)",
|
54
57
|
default=1.0,
|
55
58
|
),
|
@@ -57,6 +60,7 @@ class SentrySettings(OpaqueSettings):
|
|
57
60
|
traces_sample_rate: Annotated[
|
58
61
|
float,
|
59
62
|
Field(
|
63
|
+
ge=0.0,
|
60
64
|
description="Traces Sample Rate (https://docs.sentry.io/platforms/python/configuration/sampling/#configuring-the-transaction-sample-rate)",
|
61
65
|
default=1.0,
|
62
66
|
),
|
@@ -64,6 +68,7 @@ class SentrySettings(OpaqueSettings):
|
|
64
68
|
profiles_sample_rate: Annotated[
|
65
69
|
float,
|
66
70
|
Field(
|
71
|
+
ge=0.0,
|
67
72
|
description="Traces Sample Rate (https://docs.sentry.io/platforms/python/tracing/#configure)",
|
68
73
|
default=1.0,
|
69
74
|
),
|
@@ -84,7 +89,7 @@ def sentry_initialize() -> bool:
|
|
84
89
|
sentry_sdk.init(
|
85
90
|
release=f"{__project_name__}@{__version__}", # https://docs.sentry.io/platforms/python/configuration/releases/,
|
86
91
|
environment=__env__,
|
87
|
-
dsn=settings.dsn.get_secret_value(),
|
92
|
+
dsn=settings.dsn.get_secret_value().strip(),
|
88
93
|
max_breadcrumbs=settings.max_breadcrumbs,
|
89
94
|
debug=settings.debug,
|
90
95
|
send_default_pii=settings.send_default_pii,
|
@@ -20,6 +20,15 @@ logger = logging.getLogger(__name__)
|
|
20
20
|
UNHIDE_SENSITIVE_INFO = "unhide_sensitive_info"
|
21
21
|
|
22
22
|
|
23
|
+
def strip_to_none_before_validator(v: str | None) -> str | None:
|
24
|
+
if v is None:
|
25
|
+
return None
|
26
|
+
v = v.strip()
|
27
|
+
if not v:
|
28
|
+
return None
|
29
|
+
return v
|
30
|
+
|
31
|
+
|
23
32
|
class OpaqueSettings(BaseSettings):
|
24
33
|
@staticmethod
|
25
34
|
def serialize_sensitive_info(input_value: SecretStr, info: FieldSerializationInfo) -> str | None:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|