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.
@@ -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
+ ![PyPI - License](https://img.shields.io/pypi/l/dynaconf-tortoise-loader?logo=pypi)
32
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/dynaconf-tortoise-loader?logo=pypi)
33
+ ![PyPI - Version](https://img.shields.io/pypi/v/dynaconf-tortoise-loader?logo=pypi)
34
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dynaconf-tortoise-loader?logo=pypi)
35
+
36
+ ![release](https://github.com/Katulos/dynaconf-tortoise-loader/actions/workflows/release.yml/badge.svg)
37
+ ![develop](https://github.com/Katulos/dynaconf-tortoise-loader/actions/workflows/develop.yml/badge.svg?branch=develop)
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
+ ![PyPI - License](https://img.shields.io/pypi/l/dynaconf-tortoise-loader?logo=pypi)
4
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/dynaconf-tortoise-loader?logo=pypi)
5
+ ![PyPI - Version](https://img.shields.io/pypi/v/dynaconf-tortoise-loader?logo=pypi)
6
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dynaconf-tortoise-loader?logo=pypi)
7
+
8
+ ![release](https://github.com/Katulos/dynaconf-tortoise-loader/actions/workflows/release.yml/badge.svg)
9
+ ![develop](https://github.com/Katulos/dynaconf-tortoise-loader/actions/workflows/develop.yml/badge.svg?branch=develop)
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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ![PyPI - License](https://img.shields.io/pypi/l/dynaconf-tortoise-loader?logo=pypi)
32
+ ![PyPI - Wheel](https://img.shields.io/pypi/wheel/dynaconf-tortoise-loader?logo=pypi)
33
+ ![PyPI - Version](https://img.shields.io/pypi/v/dynaconf-tortoise-loader?logo=pypi)
34
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dynaconf-tortoise-loader?logo=pypi)
35
+
36
+ ![release](https://github.com/Katulos/dynaconf-tortoise-loader/actions/workflows/release.yml/badge.svg)
37
+ ![develop](https://github.com/Katulos/dynaconf-tortoise-loader/actions/workflows/develop.yml/badge.svg?branch=develop)
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,2 @@
1
+ dynaconf>=3.2.12
2
+ tortoise-orm>=1.1.4
@@ -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"