snakeboot 0.2.0__tar.gz → 0.3.0__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.
- {snakeboot-0.2.0 → snakeboot-0.3.0}/PKG-INFO +12 -10
- {snakeboot-0.2.0 → snakeboot-0.3.0}/README.md +11 -9
- {snakeboot-0.2.0 → snakeboot-0.3.0}/pyproject.toml +1 -1
- {snakeboot-0.2.0 → snakeboot-0.3.0}/pyproject.toml.orig +1 -1
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/__init__.py +8 -2
- snakeboot-0.3.0/src/snakeboot/configuration/cache_properties.py +16 -0
- snakeboot-0.3.0/src/snakeboot/configuration/database_properties.py +29 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/django_properties.py +3 -4
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/properties_rules.py +2 -2
- snakeboot-0.3.0/src/snakeboot/configuration/section.py +12 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/LICENSE +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/component_scan/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/component_scan/application_root.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/component_scan/component_scan.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/configuration.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/configuration_errors.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/configuration_properties.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/configuration/load_configuration.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/django/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/django/django_atomic_transaction_manager.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/events/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/events/application_event_listener.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/events/application_event_publisher.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/events/application_event_publisher_injectable.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/events/event_listener_markers.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/py.typed +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/authenticated_principal.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/controller_endpoint_factory.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/error_answer.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/rest_controller.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/rest_controller_advice.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/rest_controller_errors.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/rest_controller_mappings.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/rest_controller_mounter.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/rest_controller_scanner.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/rest/route_markers.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/testing/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/testing/declared_classes.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/testing/recording_transaction_manager.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/__init__.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/after_commit.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/transaction_manager.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/transaction_manager_not_installed_error.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/transaction_manager_registry.py +0 -0
- {snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/transactional.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: snakeboot
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Spring Boot ergonomics for hexagonal Python: @transactional, application events, a component scan, typed configuration with profiles and singleton REST controllers on Django Ninja, wired by wireup.
|
|
5
5
|
Keywords: spring-boot,hexagonal-architecture,ddd,django,django-ninja,wireup,dependency-injection,transactional
|
|
6
6
|
Author: Wouter Deketelaere
|
|
@@ -48,7 +48,8 @@ classes you can read in an afternoon, and none of them knows anything about your
|
|
|
48
48
|
| `ApplicationEventPublisher`, `@event_listener`, `@transactional_event_listener` | the same names | wireup |
|
|
49
49
|
| `@rest_controller`, `@route.get`, `@rest_controller_advice`, `@exception_handler` | `@RestController`, `@GetMapping`, `@RestControllerAdvice`, `@ExceptionHandler` | `snakeboot[ninja]` |
|
|
50
50
|
| `DjangoAtomicTransactionManager` | `PlatformTransactionManager` for JDBC | `snakeboot[django]` |
|
|
51
|
-
| `@configuration_properties`, `load_configuration`, `application-<profile>.toml` | `@ConfigurationProperties`, `@EnableConfigurationProperties`, profiles | `snakeboot[configuration]` |
|
|
51
|
+
| `@configuration_properties`, `Section`, `load_configuration`, `application-<profile>.toml` | `@ConfigurationProperties`, `@EnableConfigurationProperties`, profiles | `snakeboot[configuration]` |
|
|
52
|
+
| `DjangoProperties`, `DatabaseProperties`, `CacheProperties` | `server.*`, `spring.datasource.*`, `spring.data.redis.*` | `snakeboot[configuration]` |
|
|
52
53
|
| `RecordingTransactionManager`, `declared_classes` | spring-test | nothing |
|
|
53
54
|
|
|
54
55
|
The transaction, component scan and testing packages import nothing but the standard library,
|
|
@@ -76,22 +77,23 @@ class MyApplication: ...
|
|
|
76
77
|
WIREUP = WireupSettings(injectables=[*ComponentScan.of(MyApplication).module_names()], auto_inject_views=False)
|
|
77
78
|
```
|
|
78
79
|
|
|
79
|
-
**Typed configuration.** A Pydantic model is bound to one
|
|
80
|
-
`@ConfigurationProperties`. Settings lists the sections once, like
|
|
81
|
-
every section then reaches wireup by its type.
|
|
80
|
+
**Typed configuration.** A `Section` (a frozen Pydantic model that refuses unknown keys) is bound to one
|
|
81
|
+
table of `application.toml`, like `@ConfigurationProperties`. Settings lists the sections once, like
|
|
82
|
+
`@EnableConfigurationProperties`; every section then reaches wireup by its type. The sections Django itself
|
|
83
|
+
needs come with snakeboot: `DjangoProperties` (`[django]`), `DatabaseProperties` (`[database]`, PostgreSQL) and
|
|
84
|
+
`CacheProperties` (`[cache]`, Redis).
|
|
82
85
|
|
|
83
86
|
```python
|
|
84
87
|
@configuration_properties("chatbot")
|
|
85
|
-
class ChatbotProperties(
|
|
86
|
-
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
87
|
-
|
|
88
|
+
class ChatbotProperties(Section):
|
|
88
89
|
model: str # no default: the value is in application.toml
|
|
89
|
-
llm: LanguageModelProperties # a nested
|
|
90
|
+
llm: LanguageModelProperties # a nested Section is the table [chatbot.llm]
|
|
90
91
|
|
|
91
92
|
|
|
92
93
|
# settings.py
|
|
93
|
-
CONFIGURATION = load_configuration(DjangoProperties, ChatbotProperties, package="myapp")
|
|
94
|
+
CONFIGURATION = load_configuration(DjangoProperties, DatabaseProperties, ChatbotProperties, package="myapp")
|
|
94
95
|
SECRET_KEY = CONFIGURATION.get(DjangoProperties).secret_key.get_secret_value()
|
|
96
|
+
DATABASES = {"default": CONFIGURATION.get(DatabaseProperties).as_django_database()}
|
|
95
97
|
WIREUP = WireupSettings(injectables=[*ComponentScan.of(MyApplication).module_names(), *CONFIGURATION.injectables()])
|
|
96
98
|
```
|
|
97
99
|
|
|
@@ -13,7 +13,8 @@ classes you can read in an afternoon, and none of them knows anything about your
|
|
|
13
13
|
| `ApplicationEventPublisher`, `@event_listener`, `@transactional_event_listener` | the same names | wireup |
|
|
14
14
|
| `@rest_controller`, `@route.get`, `@rest_controller_advice`, `@exception_handler` | `@RestController`, `@GetMapping`, `@RestControllerAdvice`, `@ExceptionHandler` | `snakeboot[ninja]` |
|
|
15
15
|
| `DjangoAtomicTransactionManager` | `PlatformTransactionManager` for JDBC | `snakeboot[django]` |
|
|
16
|
-
| `@configuration_properties`, `load_configuration`, `application-<profile>.toml` | `@ConfigurationProperties`, `@EnableConfigurationProperties`, profiles | `snakeboot[configuration]` |
|
|
16
|
+
| `@configuration_properties`, `Section`, `load_configuration`, `application-<profile>.toml` | `@ConfigurationProperties`, `@EnableConfigurationProperties`, profiles | `snakeboot[configuration]` |
|
|
17
|
+
| `DjangoProperties`, `DatabaseProperties`, `CacheProperties` | `server.*`, `spring.datasource.*`, `spring.data.redis.*` | `snakeboot[configuration]` |
|
|
17
18
|
| `RecordingTransactionManager`, `declared_classes` | spring-test | nothing |
|
|
18
19
|
|
|
19
20
|
The transaction, component scan and testing packages import nothing but the standard library,
|
|
@@ -41,22 +42,23 @@ class MyApplication: ...
|
|
|
41
42
|
WIREUP = WireupSettings(injectables=[*ComponentScan.of(MyApplication).module_names()], auto_inject_views=False)
|
|
42
43
|
```
|
|
43
44
|
|
|
44
|
-
**Typed configuration.** A Pydantic model is bound to one
|
|
45
|
-
`@ConfigurationProperties`. Settings lists the sections once, like
|
|
46
|
-
every section then reaches wireup by its type.
|
|
45
|
+
**Typed configuration.** A `Section` (a frozen Pydantic model that refuses unknown keys) is bound to one
|
|
46
|
+
table of `application.toml`, like `@ConfigurationProperties`. Settings lists the sections once, like
|
|
47
|
+
`@EnableConfigurationProperties`; every section then reaches wireup by its type. The sections Django itself
|
|
48
|
+
needs come with snakeboot: `DjangoProperties` (`[django]`), `DatabaseProperties` (`[database]`, PostgreSQL) and
|
|
49
|
+
`CacheProperties` (`[cache]`, Redis).
|
|
47
50
|
|
|
48
51
|
```python
|
|
49
52
|
@configuration_properties("chatbot")
|
|
50
|
-
class ChatbotProperties(
|
|
51
|
-
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
52
|
-
|
|
53
|
+
class ChatbotProperties(Section):
|
|
53
54
|
model: str # no default: the value is in application.toml
|
|
54
|
-
llm: LanguageModelProperties # a nested
|
|
55
|
+
llm: LanguageModelProperties # a nested Section is the table [chatbot.llm]
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
# settings.py
|
|
58
|
-
CONFIGURATION = load_configuration(DjangoProperties, ChatbotProperties, package="myapp")
|
|
59
|
+
CONFIGURATION = load_configuration(DjangoProperties, DatabaseProperties, ChatbotProperties, package="myapp")
|
|
59
60
|
SECRET_KEY = CONFIGURATION.get(DjangoProperties).secret_key.get_secret_value()
|
|
61
|
+
DATABASES = {"default": CONFIGURATION.get(DatabaseProperties).as_django_database()}
|
|
60
62
|
WIREUP = WireupSettings(injectables=[*ComponentScan.of(MyApplication).module_names(), *CONFIGURATION.injectables()])
|
|
61
63
|
```
|
|
62
64
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "snakeboot"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Spring Boot ergonomics for hexagonal Python: @transactional, application events, a component scan, typed configuration with profiles and singleton REST controllers on Django Ninja, wired by wireup."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "snakeboot"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Spring Boot ergonomics for hexagonal Python: @transactional, application events, a component scan, typed configuration with profiles and singleton REST controllers on Django Ninja, wired by wireup."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "MIT"
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"""Technical library: typed configuration from application.toml, one profile and the environment.
|
|
2
2
|
|
|
3
3
|
@configuration_properties("chatbot") # @ConfigurationProperties(prefix = "chatbot")
|
|
4
|
-
class ChatbotProperties(
|
|
5
|
-
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
4
|
+
class ChatbotProperties(Section):
|
|
6
5
|
model: str # no default: the value is in application.toml
|
|
7
6
|
|
|
8
7
|
# in settings.py, like @EnableConfigurationProperties({DjangoProperties.class, ChatbotProperties.class}):
|
|
@@ -12,24 +11,31 @@
|
|
|
12
11
|
|
|
13
12
|
The values come from `application.toml` in the package, then `application-<profile>.toml` when
|
|
14
13
|
SNAKEBOOT_PROFILE is set, then environment variables such as CHATBOT__MODEL; nothing else.
|
|
14
|
+
`DjangoProperties`, `DatabaseProperties` and `CacheProperties` are the sections Django itself needs.
|
|
15
15
|
"""
|
|
16
16
|
|
|
17
|
+
from snakeboot.configuration.cache_properties import CacheProperties
|
|
17
18
|
from snakeboot.configuration.configuration import Configuration
|
|
18
19
|
from snakeboot.configuration.configuration_errors import (
|
|
19
20
|
ConfigurationFileNotFoundError,
|
|
20
21
|
InvalidConfigurationPropertiesError,
|
|
21
22
|
)
|
|
22
23
|
from snakeboot.configuration.configuration_properties import configuration_properties
|
|
24
|
+
from snakeboot.configuration.database_properties import DatabaseProperties
|
|
23
25
|
from snakeboot.configuration.django_properties import DjangoProperties
|
|
24
26
|
from snakeboot.configuration.load_configuration import BASE_FILE, PROFILE_VARIABLE, load_configuration
|
|
27
|
+
from snakeboot.configuration.section import Section
|
|
25
28
|
|
|
26
29
|
__all__ = [
|
|
27
30
|
"BASE_FILE",
|
|
28
31
|
"PROFILE_VARIABLE",
|
|
32
|
+
"CacheProperties",
|
|
29
33
|
"Configuration",
|
|
30
34
|
"ConfigurationFileNotFoundError",
|
|
35
|
+
"DatabaseProperties",
|
|
31
36
|
"DjangoProperties",
|
|
32
37
|
"InvalidConfigurationPropertiesError",
|
|
38
|
+
"Section",
|
|
33
39
|
"configuration_properties",
|
|
34
40
|
"load_configuration",
|
|
35
41
|
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from snakeboot.configuration.configuration_properties import configuration_properties
|
|
2
|
+
from snakeboot.configuration.section import Section
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@configuration_properties("cache")
|
|
6
|
+
class CacheProperties(Section):
|
|
7
|
+
"""The section `[cache]`: the Redis that backs Django's cache, like Spring Boot's `spring.data.redis.*`.
|
|
8
|
+
|
|
9
|
+
CACHES = {"default": CONFIGURATION.get(CacheProperties).as_django_cache()}
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
redis_url: str
|
|
13
|
+
|
|
14
|
+
def as_django_cache(self) -> dict[str, str]:
|
|
15
|
+
"""The entry of Django's `CACHES` setting."""
|
|
16
|
+
return {"BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": self.redis_url}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from pydantic import SecretStr
|
|
2
|
+
|
|
3
|
+
from snakeboot.configuration.configuration_properties import configuration_properties
|
|
4
|
+
from snakeboot.configuration.section import Section
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@configuration_properties("database")
|
|
8
|
+
class DatabaseProperties(Section):
|
|
9
|
+
"""The section `[database]`: the PostgreSQL database of the service, like Spring Boot's `spring.datasource.*`.
|
|
10
|
+
|
|
11
|
+
DATABASES = {"default": CONFIGURATION.get(DatabaseProperties).as_django_database()}
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
host: str
|
|
15
|
+
port: int
|
|
16
|
+
name: str
|
|
17
|
+
user: str
|
|
18
|
+
password: SecretStr
|
|
19
|
+
|
|
20
|
+
def as_django_database(self) -> dict[str, str]:
|
|
21
|
+
"""The entry of Django's `DATABASES` setting."""
|
|
22
|
+
return {
|
|
23
|
+
"ENGINE": "django.db.backends.postgresql",
|
|
24
|
+
"HOST": self.host,
|
|
25
|
+
"PORT": str(self.port),
|
|
26
|
+
"NAME": self.name,
|
|
27
|
+
"USER": self.user,
|
|
28
|
+
"PASSWORD": self.password.get_secret_value(),
|
|
29
|
+
}
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
from typing import Literal
|
|
2
2
|
|
|
3
|
-
from pydantic import
|
|
3
|
+
from pydantic import SecretStr
|
|
4
4
|
|
|
5
5
|
from snakeboot.configuration.configuration_properties import configuration_properties
|
|
6
|
+
from snakeboot.configuration.section import Section
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@configuration_properties("django")
|
|
9
|
-
class DjangoProperties(
|
|
10
|
+
class DjangoProperties(Section):
|
|
10
11
|
"""The section `[django]`: what settings.py hands to Django itself, like Spring Boot's `server.*` properties.
|
|
11
12
|
|
|
12
13
|
`secret_key` is a SecretStr, so it never shows in a log or a traceback; settings.py unwraps it once.
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
|
-
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
16
|
-
|
|
17
16
|
secret_key: SecretStr
|
|
18
17
|
debug: bool
|
|
19
18
|
allowed_hosts: tuple[str, ...]
|
|
@@ -42,8 +42,8 @@ def _models_named_by(annotation: object) -> Iterator[type[BaseModel]]:
|
|
|
42
42
|
def _refuse_open_model(model: type[BaseModel]) -> None:
|
|
43
43
|
if model.model_config.get("extra") != "forbid" or model.model_config.get("frozen") is not True:
|
|
44
44
|
raise InvalidConfigurationPropertiesError(
|
|
45
|
-
f
|
|
46
|
-
"
|
|
45
|
+
f"{model.__name__} must extend Section (extra='forbid', frozen=True): an unknown key is a typo that "
|
|
46
|
+
"must stop the start, and the configuration does not change while running."
|
|
47
47
|
)
|
|
48
48
|
|
|
49
49
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from pydantic import BaseModel, ConfigDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Section(BaseModel):
|
|
5
|
+
"""A table of application.toml as a class: a frozen Pydantic model that refuses unknown keys.
|
|
6
|
+
|
|
7
|
+
Every section and every table nested in it extends `Section`, so a typo in a key stops the start and a value
|
|
8
|
+
cannot change while the application runs. Give no field a default: every value is written in application.toml
|
|
9
|
+
or in a profile, where everyone can read it.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
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
|
{snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/django/django_atomic_transaction_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/events/application_event_publisher_injectable.py
RENAMED
|
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
|
{snakeboot-0.2.0 → snakeboot-0.3.0}/src/snakeboot/transaction/transaction_manager_registry.py
RENAMED
|
File without changes
|
|
File without changes
|