dynaconf-tortoise-loader 0.1.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.
- dynaconf_tortoise_loader-0.1.0/LICENSE +21 -0
- dynaconf_tortoise_loader-0.1.0/PKG-INFO +127 -0
- dynaconf_tortoise_loader-0.1.0/README.md +99 -0
- dynaconf_tortoise_loader-0.1.0/pyproject.toml +48 -0
- dynaconf_tortoise_loader-0.1.0/setup.cfg +4 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader/__init__.py +1 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader/loader.py +206 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader/models.py +34 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader.egg-info/PKG-INFO +127 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader.egg-info/SOURCES.txt +12 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader.egg-info/dependency_links.txt +1 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader.egg-info/requires.txt +2 -0
- dynaconf_tortoise_loader-0.1.0/src/dynaconf_tortoise_loader.egg-info/top_level.txt +1 -0
- dynaconf_tortoise_loader-0.1.0/tests/test_tortoise.py +51 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Katulos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dynaconf-tortoise-loader
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Author-email: Katulos <katulos@protonmail.com>
|
|
5
|
+
Maintainer-email: Katulos <katulos@protonmail.com>
|
|
6
|
+
Project-URL: Documentation, https://github.com/katulos/dynaconf-tortoise-loader/tree/master#readme
|
|
7
|
+
Project-URL: Home, https://github.com/katulos/dynaconf-tortoise-loader
|
|
8
|
+
Project-URL: Source, https://github.com/katulos/dynaconf-tortoise-loader
|
|
9
|
+
Project-URL: Tracker, https://github.com/katulos/dynaconf-tortoise-loader/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: <3.14,>=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: dynaconf>=3.2.12
|
|
26
|
+
Requires-Dist: tortoise-orm>=1.1.4
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Dynaconf Tortoise ORM Loader
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+

|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
`pip install dynaconf-tortoise-loader`
|
|
42
|
+
By default, Tortoise ORM comes with a sqlite database driver.
|
|
43
|
+
If you require a different database driver, please refer to the [Tortoise ORM documentation](https://tortoise.github.io/getting_started.html#installation).
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
1. Create a config.py file in your project
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import logging
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
from dynaconf import Dynaconf, ValidationError
|
|
54
|
+
from dynaconf.utils.boxing import DynaBox
|
|
55
|
+
from dynaconf.utils.functional import empty
|
|
56
|
+
|
|
57
|
+
from dynaconf_tortoise_loader import loader
|
|
58
|
+
|
|
59
|
+
_BASE_DIR = Path.cwd()
|
|
60
|
+
_DB_PATH = _BASE_DIR / "data" / "settings.sqlite3"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Config(Dynaconf):
|
|
64
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
self._initialized = True
|
|
67
|
+
|
|
68
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
69
|
+
if name not in [
|
|
70
|
+
"_wrapped",
|
|
71
|
+
"_kwargs",
|
|
72
|
+
"_warn_dynaconf_global_settings",
|
|
73
|
+
]:
|
|
74
|
+
if self._wrapped is empty:
|
|
75
|
+
self._setup()
|
|
76
|
+
|
|
77
|
+
super().__setattr__(name, value)
|
|
78
|
+
|
|
79
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
80
|
+
try:
|
|
81
|
+
data = DynaBox({name: value}, box_settings={}).to_dict()
|
|
82
|
+
loader.write(self, data)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logging.error(e)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
settings = Config(
|
|
88
|
+
# Define the environments to use
|
|
89
|
+
# environments=True,
|
|
90
|
+
envvar_prefix="MYAPP",
|
|
91
|
+
settings_files=[
|
|
92
|
+
_BASE_DIR / "settings.toml",
|
|
93
|
+
_BASE_DIR / ".secrets.toml",
|
|
94
|
+
],
|
|
95
|
+
LOADERS_FOR_DYNACONF=[
|
|
96
|
+
"dynaconf_tortoise_loader.loader", # require custom loader
|
|
97
|
+
"dynaconf.loaders.env_loader", # require dotenv loader
|
|
98
|
+
],
|
|
99
|
+
TORTOISE_ENABLED_FOR_DYNACONF=True,
|
|
100
|
+
TORTOISE_URL_FOR_DYNACONF=f"sqlite://{_DB_PATH}",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
settings.validators.validate_all()
|
|
105
|
+
except ValidationError as e:
|
|
106
|
+
logging.error(e.message)
|
|
107
|
+
```
|
|
108
|
+
The `TORTOISE_URL_FOR_DYNACONF`value must contain the database connection string. See the [Tortoise ORM documentation](https://tortoise.github.io/databases.html#db-url) for more details.
|
|
109
|
+
`TORTOISE_URL_FOR_DYNACONF` does not need to be declared directly in the code, but can be declared in the settings files listed in `settings_files` or set via an environment variable.
|
|
110
|
+
For more information, please refer to the [Dynaconf documentation](https://www.dynaconf.com/configuration/).
|
|
111
|
+
|
|
112
|
+
2. Use the `settings` object in your code
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
form .config import settings
|
|
116
|
+
|
|
117
|
+
# set and store settings
|
|
118
|
+
settings.host = "localhost"
|
|
119
|
+
settings.port = 8022
|
|
120
|
+
|
|
121
|
+
# get settings
|
|
122
|
+
print(settings.get("host"))
|
|
123
|
+
print(settings.get("port"))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
See [more example](examples/sqlite)
|
|
127
|
+
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Dynaconf Tortoise ORM Loader
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
`pip install dynaconf-tortoise-loader`
|
|
14
|
+
By default, Tortoise ORM comes with a sqlite database driver.
|
|
15
|
+
If you require a different database driver, please refer to the [Tortoise ORM documentation](https://tortoise.github.io/getting_started.html#installation).
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
1. Create a config.py file in your project
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
import logging
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from dynaconf import Dynaconf, ValidationError
|
|
26
|
+
from dynaconf.utils.boxing import DynaBox
|
|
27
|
+
from dynaconf.utils.functional import empty
|
|
28
|
+
|
|
29
|
+
from dynaconf_tortoise_loader import loader
|
|
30
|
+
|
|
31
|
+
_BASE_DIR = Path.cwd()
|
|
32
|
+
_DB_PATH = _BASE_DIR / "data" / "settings.sqlite3"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class Config(Dynaconf):
|
|
36
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
37
|
+
super().__init__(*args, **kwargs)
|
|
38
|
+
self._initialized = True
|
|
39
|
+
|
|
40
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
41
|
+
if name not in [
|
|
42
|
+
"_wrapped",
|
|
43
|
+
"_kwargs",
|
|
44
|
+
"_warn_dynaconf_global_settings",
|
|
45
|
+
]:
|
|
46
|
+
if self._wrapped is empty:
|
|
47
|
+
self._setup()
|
|
48
|
+
|
|
49
|
+
super().__setattr__(name, value)
|
|
50
|
+
|
|
51
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
52
|
+
try:
|
|
53
|
+
data = DynaBox({name: value}, box_settings={}).to_dict()
|
|
54
|
+
loader.write(self, data)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logging.error(e)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
settings = Config(
|
|
60
|
+
# Define the environments to use
|
|
61
|
+
# environments=True,
|
|
62
|
+
envvar_prefix="MYAPP",
|
|
63
|
+
settings_files=[
|
|
64
|
+
_BASE_DIR / "settings.toml",
|
|
65
|
+
_BASE_DIR / ".secrets.toml",
|
|
66
|
+
],
|
|
67
|
+
LOADERS_FOR_DYNACONF=[
|
|
68
|
+
"dynaconf_tortoise_loader.loader", # require custom loader
|
|
69
|
+
"dynaconf.loaders.env_loader", # require dotenv loader
|
|
70
|
+
],
|
|
71
|
+
TORTOISE_ENABLED_FOR_DYNACONF=True,
|
|
72
|
+
TORTOISE_URL_FOR_DYNACONF=f"sqlite://{_DB_PATH}",
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
settings.validators.validate_all()
|
|
77
|
+
except ValidationError as e:
|
|
78
|
+
logging.error(e.message)
|
|
79
|
+
```
|
|
80
|
+
The `TORTOISE_URL_FOR_DYNACONF`value must contain the database connection string. See the [Tortoise ORM documentation](https://tortoise.github.io/databases.html#db-url) for more details.
|
|
81
|
+
`TORTOISE_URL_FOR_DYNACONF` does not need to be declared directly in the code, but can be declared in the settings files listed in `settings_files` or set via an environment variable.
|
|
82
|
+
For more information, please refer to the [Dynaconf documentation](https://www.dynaconf.com/configuration/).
|
|
83
|
+
|
|
84
|
+
2. Use the `settings` object in your code
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
form .config import settings
|
|
88
|
+
|
|
89
|
+
# set and store settings
|
|
90
|
+
settings.host = "localhost"
|
|
91
|
+
settings.port = 8022
|
|
92
|
+
|
|
93
|
+
# get settings
|
|
94
|
+
print(settings.get("host"))
|
|
95
|
+
print(settings.get("port"))
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [more example](examples/sqlite)
|
|
99
|
+
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dynaconf-tortoise-loader"
|
|
3
|
+
description = ""
|
|
4
|
+
keywords = []
|
|
5
|
+
dynamic = [ "version" ]
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Katulos", email = "katulos@protonmail.com" }
|
|
8
|
+
]
|
|
9
|
+
maintainers = [
|
|
10
|
+
{ name = "Katulos", email = "katulos@protonmail.com"}
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license-files = ["LICEN[CS]E*"]
|
|
14
|
+
requires-python = ">=3.10,<3.14"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Natural Language :: English",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Programming Language :: Python",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"dynaconf>=3.2.12",
|
|
31
|
+
"tortoise-orm>=1.1.4",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Documentation = "https://github.com/katulos/dynaconf-tortoise-loader/tree/master#readme"
|
|
36
|
+
Home = "https://github.com/katulos/dynaconf-tortoise-loader"
|
|
37
|
+
Source = "https://github.com/katulos/dynaconf-tortoise-loader"
|
|
38
|
+
Tracker = "https://github.com/katulos/dynaconf-tortoise-loader/issues"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.dynamic]
|
|
41
|
+
version = {attr = "dynaconf_tortoise_loader.__version__"}
|
|
42
|
+
|
|
43
|
+
[tool.setuptools.packages.find]
|
|
44
|
+
where = ["src"]
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["setuptools"]
|
|
48
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from dynaconf import Dynaconf
|
|
4
|
+
from dynaconf.loaders.base import SourceMetadata
|
|
5
|
+
from dynaconf.utils import build_env_list, upperfy
|
|
6
|
+
from dynaconf.utils.parse_conf import parse_conf_data, unparse_conf_data
|
|
7
|
+
|
|
8
|
+
from dynaconf_tortoise_loader.models import DynaconfStorage
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from tortoise import Tortoise, run_async
|
|
12
|
+
except ImportError as _tie:
|
|
13
|
+
raise ImportError(
|
|
14
|
+
"Tortoise-ORM package is not installed in your environment. "
|
|
15
|
+
"`pip install tortoise-orm` or disable the tortoise loader with "
|
|
16
|
+
"export TORTOISE_ENABLED_FOR_DYNACONF=false",
|
|
17
|
+
) from _tie
|
|
18
|
+
|
|
19
|
+
IDENTIFIER = "tortoise"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def _init_tortoise(obj: Dynaconf) -> None:
|
|
23
|
+
db_url = obj.get("TORTOISE_URL_FOR_DYNACONF")
|
|
24
|
+
if not db_url:
|
|
25
|
+
raise ValueError("TORTOISE_URL_FOR_DYNACONF is required")
|
|
26
|
+
|
|
27
|
+
config = {
|
|
28
|
+
"connections": {"default": db_url},
|
|
29
|
+
"apps": {
|
|
30
|
+
"dynaconf_tortoise_loader": {
|
|
31
|
+
"models": ["dynaconf_tortoise_loader.models"],
|
|
32
|
+
"default_connection": "default",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
await Tortoise.init(config)
|
|
37
|
+
await Tortoise.generate_schemas(safe=True)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
async def _load_data(holder: str, key: str | None = None) -> dict[str, Any]:
|
|
41
|
+
if key:
|
|
42
|
+
record = await DynaconfStorage.get_or_none(
|
|
43
|
+
holder=holder.upper(),
|
|
44
|
+
key=upperfy(key),
|
|
45
|
+
)
|
|
46
|
+
if record:
|
|
47
|
+
return {key: record.value}
|
|
48
|
+
return {}
|
|
49
|
+
else:
|
|
50
|
+
records = await DynaconfStorage.filter(holder=holder).all()
|
|
51
|
+
return {record.key: record.value for record in records}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _save_data(holder: str, data: dict[str, Any]) -> None:
|
|
55
|
+
for key, value in data.items():
|
|
56
|
+
await DynaconfStorage.update_or_create(
|
|
57
|
+
holder=holder.upper(),
|
|
58
|
+
key=upperfy(key),
|
|
59
|
+
defaults={"value": value},
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def _delete_data(holder: str, key: str | None = None) -> None:
|
|
64
|
+
if key:
|
|
65
|
+
await DynaconfStorage.filter(
|
|
66
|
+
holder=holder.upper(),
|
|
67
|
+
key=upperfy(key),
|
|
68
|
+
).delete()
|
|
69
|
+
else:
|
|
70
|
+
await DynaconfStorage.filter(holder=holder.upper()).delete()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def load(
|
|
74
|
+
obj: Dynaconf,
|
|
75
|
+
env: str | None = None,
|
|
76
|
+
silent: bool = True,
|
|
77
|
+
key: str | None = None,
|
|
78
|
+
validate: bool = False,
|
|
79
|
+
) -> None:
|
|
80
|
+
prefix = obj.get("ENVVAR_PREFIX_FOR_DYNACONF")
|
|
81
|
+
env_name = env or obj.current_env
|
|
82
|
+
env_list = build_env_list(obj, env_name)
|
|
83
|
+
|
|
84
|
+
if prefix and prefix not in env_list:
|
|
85
|
+
env_list.insert(0, prefix)
|
|
86
|
+
|
|
87
|
+
async def _inner() -> None:
|
|
88
|
+
await _init_tortoise(obj)
|
|
89
|
+
try:
|
|
90
|
+
all_data = {}
|
|
91
|
+
|
|
92
|
+
for search_env in env_list:
|
|
93
|
+
if prefix:
|
|
94
|
+
if search_env == prefix:
|
|
95
|
+
holder = f"{prefix.upper()}"
|
|
96
|
+
else:
|
|
97
|
+
holder = f"{prefix.upper()}_{search_env.upper()}"
|
|
98
|
+
else:
|
|
99
|
+
holder = search_env.upper()
|
|
100
|
+
|
|
101
|
+
raw_data = await _load_data(holder, key=key)
|
|
102
|
+
if raw_data:
|
|
103
|
+
parsed_data = {
|
|
104
|
+
k: parse_conf_data(v, tomlfy=True, box_settings=obj)
|
|
105
|
+
for k, v in raw_data.items()
|
|
106
|
+
}
|
|
107
|
+
all_data.update(parsed_data)
|
|
108
|
+
|
|
109
|
+
if key and key in parsed_data:
|
|
110
|
+
break
|
|
111
|
+
|
|
112
|
+
if all_data:
|
|
113
|
+
source_metadata = SourceMetadata(
|
|
114
|
+
IDENTIFIER,
|
|
115
|
+
"unique",
|
|
116
|
+
env_name.lower(),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if key:
|
|
120
|
+
if key in all_data:
|
|
121
|
+
obj.set(
|
|
122
|
+
key,
|
|
123
|
+
all_data[key],
|
|
124
|
+
validate=validate,
|
|
125
|
+
loader_identifier=source_metadata,
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
obj.update(
|
|
129
|
+
all_data,
|
|
130
|
+
loader_identifier=source_metadata,
|
|
131
|
+
validate=validate,
|
|
132
|
+
)
|
|
133
|
+
finally:
|
|
134
|
+
await Tortoise.close_connections()
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
run_async(_inner())
|
|
138
|
+
return
|
|
139
|
+
except Exception as e:
|
|
140
|
+
if not silent:
|
|
141
|
+
raise e
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def write(
|
|
146
|
+
obj: Dynaconf,
|
|
147
|
+
data: dict[str, Any] | None = None,
|
|
148
|
+
**kwargs: Any,
|
|
149
|
+
) -> None:
|
|
150
|
+
if obj.TORTOISE_ENABLED_FOR_DYNACONF is False:
|
|
151
|
+
raise RuntimeError(
|
|
152
|
+
"Tortoise-ORM is not configured \n"
|
|
153
|
+
"export TORTOISE_ENABLED_FOR_DYNACONF=true\n"
|
|
154
|
+
"and configure the TORTOISE_*_FOR_DYNACONF variables",
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
holder = obj.get("ENVVAR_PREFIX_FOR_DYNACONF").upper()
|
|
158
|
+
holder = f"{holder}_{obj.current_env.upper()}"
|
|
159
|
+
|
|
160
|
+
data = data or {}
|
|
161
|
+
data.update(kwargs)
|
|
162
|
+
if not data:
|
|
163
|
+
raise AttributeError("Data must be provided")
|
|
164
|
+
|
|
165
|
+
tortoise_data = {}
|
|
166
|
+
for key, value in data.items():
|
|
167
|
+
upper_key = upperfy(key)
|
|
168
|
+
unparsed_value = unparse_conf_data(value)
|
|
169
|
+
tortoise_data[upper_key] = unparsed_value
|
|
170
|
+
|
|
171
|
+
async def _inner() -> None:
|
|
172
|
+
await _init_tortoise(obj)
|
|
173
|
+
try:
|
|
174
|
+
await _save_data(holder, tortoise_data)
|
|
175
|
+
finally:
|
|
176
|
+
await Tortoise.close_connections()
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
run_async(_inner())
|
|
180
|
+
load(obj)
|
|
181
|
+
return
|
|
182
|
+
except Exception as e:
|
|
183
|
+
raise e
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def delete(obj: Dynaconf, key: str | None = None) -> None:
|
|
187
|
+
if obj.TORTOISE_ENABLED_FOR_DYNACONF is False:
|
|
188
|
+
raise RuntimeError("Tortoise loader is disabled")
|
|
189
|
+
|
|
190
|
+
holder = obj.get("ENVVAR_PREFIX_FOR_DYNACONF").upper()
|
|
191
|
+
holder = f"{holder}_{obj.current_env.upper()}"
|
|
192
|
+
|
|
193
|
+
async def _inner() -> None:
|
|
194
|
+
await _init_tortoise(obj)
|
|
195
|
+
try:
|
|
196
|
+
await _delete_data(holder, key=key)
|
|
197
|
+
finally:
|
|
198
|
+
await Tortoise.close_connections()
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
run_async(_inner())
|
|
202
|
+
if key:
|
|
203
|
+
obj.unset(key)
|
|
204
|
+
return
|
|
205
|
+
except Exception as e:
|
|
206
|
+
raise e
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from tortoise import BaseDBAsyncClient, fields
|
|
4
|
+
from tortoise.models import Model
|
|
5
|
+
from tortoise.signals import post_save
|
|
6
|
+
|
|
7
|
+
_logger = logging.getLogger("dynaconf")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DynaconfStorage(Model):
|
|
11
|
+
id = fields.BigIntField(primary_key=True)
|
|
12
|
+
holder = fields.CharField(max_length=255, db_index=True)
|
|
13
|
+
key = fields.CharField(max_length=255, db_index=True)
|
|
14
|
+
value = fields.TextField(null=True)
|
|
15
|
+
|
|
16
|
+
class Meta:
|
|
17
|
+
table = "dynaconf_storage"
|
|
18
|
+
unique_together = (("holder", "key"),)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@post_save(DynaconfStorage)
|
|
22
|
+
async def dynaconf_storage_post_save(
|
|
23
|
+
sender: type[DynaconfStorage],
|
|
24
|
+
instance: DynaconfStorage,
|
|
25
|
+
created: bool,
|
|
26
|
+
using_db: BaseDBAsyncClient | None,
|
|
27
|
+
update_fields: list[str],
|
|
28
|
+
) -> None:
|
|
29
|
+
# pylint: disable=unused-argument
|
|
30
|
+
_logger.debug(
|
|
31
|
+
"Config parameter saved: %s: %s",
|
|
32
|
+
instance.key,
|
|
33
|
+
instance.value,
|
|
34
|
+
)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dynaconf-tortoise-loader
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Author-email: Katulos <katulos@protonmail.com>
|
|
5
|
+
Maintainer-email: Katulos <katulos@protonmail.com>
|
|
6
|
+
Project-URL: Documentation, https://github.com/katulos/dynaconf-tortoise-loader/tree/master#readme
|
|
7
|
+
Project-URL: Home, https://github.com/katulos/dynaconf-tortoise-loader
|
|
8
|
+
Project-URL: Source, https://github.com/katulos/dynaconf-tortoise-loader
|
|
9
|
+
Project-URL: Tracker, https://github.com/katulos/dynaconf-tortoise-loader/issues
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: <3.14,>=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: dynaconf>=3.2.12
|
|
26
|
+
Requires-Dist: tortoise-orm>=1.1.4
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# Dynaconf Tortoise ORM Loader
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+

|
|
33
|
+

|
|
34
|
+

|
|
35
|
+
|
|
36
|
+

|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
`pip install dynaconf-tortoise-loader`
|
|
42
|
+
By default, Tortoise ORM comes with a sqlite database driver.
|
|
43
|
+
If you require a different database driver, please refer to the [Tortoise ORM documentation](https://tortoise.github.io/getting_started.html#installation).
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
1. Create a config.py file in your project
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
import logging
|
|
50
|
+
from pathlib import Path
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
from dynaconf import Dynaconf, ValidationError
|
|
54
|
+
from dynaconf.utils.boxing import DynaBox
|
|
55
|
+
from dynaconf.utils.functional import empty
|
|
56
|
+
|
|
57
|
+
from dynaconf_tortoise_loader import loader
|
|
58
|
+
|
|
59
|
+
_BASE_DIR = Path.cwd()
|
|
60
|
+
_DB_PATH = _BASE_DIR / "data" / "settings.sqlite3"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Config(Dynaconf):
|
|
64
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
65
|
+
super().__init__(*args, **kwargs)
|
|
66
|
+
self._initialized = True
|
|
67
|
+
|
|
68
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
69
|
+
if name not in [
|
|
70
|
+
"_wrapped",
|
|
71
|
+
"_kwargs",
|
|
72
|
+
"_warn_dynaconf_global_settings",
|
|
73
|
+
]:
|
|
74
|
+
if self._wrapped is empty:
|
|
75
|
+
self._setup()
|
|
76
|
+
|
|
77
|
+
super().__setattr__(name, value)
|
|
78
|
+
|
|
79
|
+
if hasattr(self, "_initialized") and self._initialized:
|
|
80
|
+
try:
|
|
81
|
+
data = DynaBox({name: value}, box_settings={}).to_dict()
|
|
82
|
+
loader.write(self, data)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logging.error(e)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
settings = Config(
|
|
88
|
+
# Define the environments to use
|
|
89
|
+
# environments=True,
|
|
90
|
+
envvar_prefix="MYAPP",
|
|
91
|
+
settings_files=[
|
|
92
|
+
_BASE_DIR / "settings.toml",
|
|
93
|
+
_BASE_DIR / ".secrets.toml",
|
|
94
|
+
],
|
|
95
|
+
LOADERS_FOR_DYNACONF=[
|
|
96
|
+
"dynaconf_tortoise_loader.loader", # require custom loader
|
|
97
|
+
"dynaconf.loaders.env_loader", # require dotenv loader
|
|
98
|
+
],
|
|
99
|
+
TORTOISE_ENABLED_FOR_DYNACONF=True,
|
|
100
|
+
TORTOISE_URL_FOR_DYNACONF=f"sqlite://{_DB_PATH}",
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
settings.validators.validate_all()
|
|
105
|
+
except ValidationError as e:
|
|
106
|
+
logging.error(e.message)
|
|
107
|
+
```
|
|
108
|
+
The `TORTOISE_URL_FOR_DYNACONF`value must contain the database connection string. See the [Tortoise ORM documentation](https://tortoise.github.io/databases.html#db-url) for more details.
|
|
109
|
+
`TORTOISE_URL_FOR_DYNACONF` does not need to be declared directly in the code, but can be declared in the settings files listed in `settings_files` or set via an environment variable.
|
|
110
|
+
For more information, please refer to the [Dynaconf documentation](https://www.dynaconf.com/configuration/).
|
|
111
|
+
|
|
112
|
+
2. Use the `settings` object in your code
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
form .config import settings
|
|
116
|
+
|
|
117
|
+
# set and store settings
|
|
118
|
+
settings.host = "localhost"
|
|
119
|
+
settings.port = 8022
|
|
120
|
+
|
|
121
|
+
# get settings
|
|
122
|
+
print(settings.get("host"))
|
|
123
|
+
print(settings.get("port"))
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
See [more example](examples/sqlite)
|
|
127
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/dynaconf_tortoise_loader/__init__.py
|
|
5
|
+
src/dynaconf_tortoise_loader/loader.py
|
|
6
|
+
src/dynaconf_tortoise_loader/models.py
|
|
7
|
+
src/dynaconf_tortoise_loader.egg-info/PKG-INFO
|
|
8
|
+
src/dynaconf_tortoise_loader.egg-info/SOURCES.txt
|
|
9
|
+
src/dynaconf_tortoise_loader.egg-info/dependency_links.txt
|
|
10
|
+
src/dynaconf_tortoise_loader.egg-info/requires.txt
|
|
11
|
+
src/dynaconf_tortoise_loader.egg-info/top_level.txt
|
|
12
|
+
tests/test_tortoise.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dynaconf_tortoise_loader
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from dynaconf.utils.inspect import get_history
|
|
3
|
+
|
|
4
|
+
from dynaconf_tortoise_loader.loader import delete, load, write
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_write_tortoise_without_data(settings):
|
|
8
|
+
with pytest.raises(AttributeError) as excinfo:
|
|
9
|
+
write(settings)
|
|
10
|
+
assert "Data must be provided" in str(excinfo.value)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_write_to_tortoise(settings):
|
|
14
|
+
write(settings, {"SECRET": "tortoise_works"})
|
|
15
|
+
load(settings, key="SECRET")
|
|
16
|
+
assert settings.get("SECRET") == "tortoise_works"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_load_from_tortoise_with_key(settings):
|
|
20
|
+
load(settings, key="SECRET")
|
|
21
|
+
assert settings.get("SECRET") == "tortoise_works"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_write_and_load_from_tortoise_without_key(settings):
|
|
25
|
+
write(settings, {"SECRET": "tortoise_works_perfectly"})
|
|
26
|
+
load(settings)
|
|
27
|
+
assert settings.get("SECRET") == "tortoise_works_perfectly"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_delete_from_tortoise(settings):
|
|
31
|
+
write(settings, {"OTHER_SECRET": "tortoise_works"})
|
|
32
|
+
load(settings)
|
|
33
|
+
assert settings.get("OTHER_SECRET") == "tortoise_works"
|
|
34
|
+
delete(settings, key="OTHER_SECRET")
|
|
35
|
+
assert load(settings, key="OTHER_SECRET") is None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_delete_all_from_tortoise(settings):
|
|
39
|
+
delete(settings)
|
|
40
|
+
assert load(settings, key="OTHER_SECRET") is None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_tortoise_has_proper_source_metadata(settings):
|
|
44
|
+
write(settings, {"SECRET": "tortoise_works_perfectly"})
|
|
45
|
+
load(settings)
|
|
46
|
+
history = get_history(
|
|
47
|
+
settings,
|
|
48
|
+
filter_callable=lambda s: s.loader == "tortoise",
|
|
49
|
+
)
|
|
50
|
+
assert history[0]["env"] == "development"
|
|
51
|
+
assert history[0]["value"]["SECRET"] == "tortoise_works_perfectly"
|