oe-python-template-example 0.3.6__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.6 → oe_python_template_example-0.3.8}/PKG-INFO +6 -4
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/README.md +4 -3
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/pyproject.toml +3 -2
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/api.py +5 -1
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_service.py +4 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_settings.py +21 -4
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_service.py +7 -1
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/__init__.py +4 -1
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_constants.py +11 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_logfire.py +3 -2
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_sentry.py +9 -4
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_settings.py +9 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/.gitignore +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/LICENSE +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/__init__.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/cli.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/constants.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/__init__.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_api.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_cli.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_constants.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/hello/_models.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/__init__.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_api.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_cli.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/system/_settings.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_api.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_cli.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_console.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_di.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_health.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_log.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_process.py +0 -0
- {oe_python_template_example-0.3.6 → oe_python_template_example-0.3.8}/src/oe_python_template_example/utils/_service.py +0 -0
- {oe_python_template_example-0.3.6 → 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/
|
@@ -63,6 +63,7 @@ Requires-Dist: pydantic-settings>=2.8.1
|
|
63
63
|
Requires-Dist: pydantic>=2.11.3
|
64
64
|
Requires-Dist: sentry-sdk>=2.25.1
|
65
65
|
Requires-Dist: typer>=0.15.1
|
66
|
+
Requires-Dist: uptime>=3.0.1
|
66
67
|
Provides-Extra: examples
|
67
68
|
Requires-Dist: jinja2>=3.1.6; extra == 'examples'
|
68
69
|
Requires-Dist: jupyter>=1.1.1; extra == 'examples'
|
@@ -162,7 +163,7 @@ Projects generated with this template come with a comprehensive development tool
|
|
162
163
|
|
163
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:
|
164
165
|
|
165
|
-
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.
|
166
167
|
2. Command-line interface (CLI) with [Typer](https://typer.tiangolo.com/)
|
167
168
|
3. Versioned webservice API with [FastAPI](https://fastapi.tiangolo.com/)
|
168
169
|
4. [Interactive Jupyter notebook](https://jupyter.org/) and [reactive Marimo notebook](https://marimo.io/)
|
@@ -172,8 +173,9 @@ Beyond development tooling, projects generated with this template include the co
|
|
172
173
|
8. Info command enabling to inspect the runtime, compiled settings, and further info provided dynamically by modules
|
173
174
|
9. Health endpoint exposing system health dynamically aggregated from all modules and dependencies
|
174
175
|
10. Flexible logging and instrumentation, including support for [Sentry](https://sentry.io/) and [Logfire](https://logfire.dev/)
|
175
|
-
11.
|
176
|
-
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
|
177
179
|
|
178
180
|
Explore [here](https://github.com/helmut-hoffer-von-ankershoffen/oe-python-template-example) for what's generated out of the box.
|
179
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 = [
|
@@ -78,6 +78,7 @@ dependencies = [
|
|
78
78
|
"pydantic-settings>=2.8.1",
|
79
79
|
"sentry-sdk>=2.25.1",
|
80
80
|
"typer>=0.15.1",
|
81
|
+
"uptime>=3.0.1",
|
81
82
|
# Custom
|
82
83
|
# Nothing yet
|
83
84
|
]
|
@@ -277,7 +278,7 @@ source = ["src/"]
|
|
277
278
|
|
278
279
|
|
279
280
|
[tool.bumpversion]
|
280
|
-
current_version = "0.3.
|
281
|
+
current_version = "0.3.8"
|
281
282
|
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
|
282
283
|
serialize = ["{major}.{minor}.{patch}"]
|
283
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
|
+
]
|
@@ -9,6 +9,7 @@ import time
|
|
9
9
|
from typing import Any
|
10
10
|
|
11
11
|
from pydantic_settings import BaseSettings
|
12
|
+
from uptime import boottime, uptime
|
12
13
|
|
13
14
|
from ..utils import ( # noqa: TID252
|
14
15
|
UNHIDE_SENSITIVE_INFO,
|
@@ -93,6 +94,7 @@ class Service(BaseService):
|
|
93
94
|
Returns:
|
94
95
|
dict[str, Any]: Service configuration.
|
95
96
|
"""
|
97
|
+
bootdatetime = boottime()
|
96
98
|
rtn = {
|
97
99
|
"package": {
|
98
100
|
"version": __version__,
|
@@ -121,6 +123,8 @@ class Service(BaseService):
|
|
121
123
|
"hostname": platform.node(),
|
122
124
|
"ip_address": platform.uname().node,
|
123
125
|
"cpu_count": os.cpu_count(),
|
126
|
+
"uptime": uptime(),
|
127
|
+
"boottime": bootdatetime.isoformat() if bootdatetime else None,
|
124
128
|
},
|
125
129
|
},
|
126
130
|
}
|
@@ -145,7 +149,9 @@ class Service(BaseService):
|
|
145
149
|
for settings_class in locate_subclasses(BaseSettings):
|
146
150
|
settings_instance = load_settings(settings_class)
|
147
151
|
env_prefix = settings_instance.model_config.get("env_prefix", "")
|
148
|
-
settings_dict =
|
152
|
+
settings_dict = json.loads(
|
153
|
+
settings_instance.model_dump_json(context={UNHIDE_SENSITIVE_INFO: not filter_secrets})
|
154
|
+
)
|
149
155
|
for key, value in settings_dict.items():
|
150
156
|
flat_key = f"{env_prefix}{key}".upper()
|
151
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
|