oe-python-template-example 0.3.7__py3-none-any.whl → 0.3.8__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.
@@ -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"http://{UVICORN_HOST}:{UVICORN_PORT}/api/{version}/docs",
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 BaseSettings, SettingsConfigDict
6
+ from pydantic import BeforeValidator, Field, PlainSerializer, SecretStr
7
+ from pydantic_settings import SettingsConfigDict
8
8
 
9
- from oe_python_template_example.utils import __env_file__, __project_name__
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(BaseSettings):
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 = settings_instance.model_dump(context={UNHIDE_SENSITIVE_INFO: not filter_secrets})
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
- default=5.0,
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template-example
3
- Version: 0.3.7
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. 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
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
 
@@ -1,5 +1,5 @@
1
1
  oe_python_template_example/__init__.py,sha256=_Z3Xb-x95UODU66avOiwROVaouk_s0ZNB25KFnPoS40,226
2
- oe_python_template_example/api.py,sha256=_DcnOEWe_MJTUWZroRnXeCh9RuBm_-x4edONWHoNBlE,2100
2
+ oe_python_template_example/api.py,sha256=cuGqjxfJocXZBZzz4UhUeUxH4Sm79tpzc6t5G-9Lp_E,2206
3
3
  oe_python_template_example/cli.py,sha256=rNcaCyYkm5_e-ITJJLUKvJfJ_RsGXQk0dnfiFCifTq8,679
4
4
  oe_python_template_example/constants.py,sha256=fm8J1LIYF9B4Ur3r5PoUdHyPSjIXWuNydHVoCbz9Jx8,171
5
5
  oe_python_template_example/hello/__init__.py,sha256=7C7gIdxkydk79-4QPyVqyUHhqP3RMFShSptzE_2J9jo,289
@@ -7,29 +7,29 @@ oe_python_template_example/hello/_api.py,sha256=B4gCojmEkvh-ScKPz0rXW70r4gVvg7SX
7
7
  oe_python_template_example/hello/_cli.py,sha256=D1RZyz8sk7wpH1a9VDx1QtsNorOr9owxK8N7SxaaMWM,1200
8
8
  oe_python_template_example/hello/_constants.py,sha256=6aRleAIcdgC13TeTzI07YwjoSwqGb2g131dw8aEoM4I,109
9
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
10
+ oe_python_template_example/hello/_service.py,sha256=o9sgk-yFX5zRi06sAOdSPqkgT93naxn-JRknwK2Nnvs,3183
11
+ oe_python_template_example/hello/_settings.py,sha256=Q14SqSvBJYFuofA-tbvBwO30sVygSaXsgaYC7x1uCfo,1562
12
12
  oe_python_template_example/system/__init__.py,sha256=NNgODkr7AyJjTTJiv3pys7o2z6xi1G96g0vnsxVhlI4,427
13
13
  oe_python_template_example/system/_api.py,sha256=rE9Aau3IIHXdEkOBUXOwJ7SxN3cZpgtYEuojnSWfT_4,3687
14
14
  oe_python_template_example/system/_cli.py,sha256=J_4upBBdbSxbONPYmOZPbuhZcKjfnPIUeZpp0L7lY-Q,4846
15
- oe_python_template_example/system/_service.py,sha256=e08VPguXKz2RhyhTbIw1ncSpE6zwKUr1xLM2WZC9u7k,6287
15
+ oe_python_template_example/system/_service.py,sha256=zdnO6e8ShgrsIK3W2KbIEYpRIp9lQw35f1UFDUn1md8,6334
16
16
  oe_python_template_example/system/_settings.py,sha256=MwMAJYifJ6jGImeSh4e9shmIXmiUSuQGHXz_Ts0mSdk,901
17
- oe_python_template_example/utils/__init__.py,sha256=rHdmSS21CtvF3AXPMSCZO17FTzxPDo-NiKZ5AjVU9b0,1449
17
+ oe_python_template_example/utils/__init__.py,sha256=MVy9A4miNYBFkNc0f24-KGG25mftb8BzDmyBb43lvQE,1559
18
18
  oe_python_template_example/utils/_api.py,sha256=w3hPQK1pL2gBI4_1qNWNa2b4S_oH-8mY-ckRX0KrCWM,617
19
19
  oe_python_template_example/utils/_cli.py,sha256=J_mFtXZ1gGeovGrE5i3wlokTOBfiTTKEz5magiRP7GA,2091
20
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
21
+ oe_python_template_example/utils/_constants.py,sha256=AP1a4kJqyf3HG6YFupUREvlkTX1b-Xx1H4u0O1nMfzc,2054
22
22
  oe_python_template_example/utils/_di.py,sha256=KdjiD4xZ_QSfbddkKWwsPJmG5YrIg6dzuBrlsd-FhxA,2189
23
23
  oe_python_template_example/utils/_health.py,sha256=35QOWe2r5InrEpGtuVMym9dI5aRHS0HWf4BHBRAUIj0,4102
24
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
25
+ oe_python_template_example/utils/_logfire.py,sha256=wZYNVowQx7kh3XJoJ59FjUKdrta7tp6cXOJRUT6lDU8,2128
26
26
  oe_python_template_example/utils/_process.py,sha256=40R0NZMqJUn0iUPERzohSUpJgU1HcJApIg1HipIxFCw,941
27
- oe_python_template_example/utils/_sentry.py,sha256=Y4hZ-PeBOdR3iRhoXW9j0tbWsYf07460UG8OVTKH1mU,3128
27
+ oe_python_template_example/utils/_sentry.py,sha256=2sXrDSZSYoDEM87v7CakJ6eGBtcIhDI48PsQCLwOHgg,3319
28
28
  oe_python_template_example/utils/_service.py,sha256=atHAejvBucKXjzhsMSdOBBFa7rRD74zcV70Pp0pl0Tg,1038
29
- oe_python_template_example/utils/_settings.py,sha256=5K1pnp-AxMQbktREb3bXDmqgrOx_L4EJIgjPQfqH4sE,2294
29
+ oe_python_template_example/utils/_settings.py,sha256=owFoaHEzJnVD3EVyOWF4rfIY7g6eLnU6rN0m4VHhCbA,2464
30
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,,
31
+ oe_python_template_example-0.3.8.dist-info/METADATA,sha256=A29u6dC905uIKGAxEMZAWb8aZpaF8JeRpX0GUnUc7eU,33326
32
+ oe_python_template_example-0.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ oe_python_template_example-0.3.8.dist-info/entry_points.txt,sha256=S2eCPB45b1Wgj_GsDRFAN-e4h7dBA5UPxT8od98erDE,82
34
+ oe_python_template_example-0.3.8.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
35
+ oe_python_template_example-0.3.8.dist-info/RECORD,,