tortoise-extensions 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ # SPDX-License-Identifier: ISC
2
+ # Copyright: 2026 NiceBots.xyz
3
+ """Extension field types for Tortoise ORM."""
4
+
5
+ try:
6
+ from tortoise_extensions._version import __version__
7
+ except ImportError:
8
+ __version__ = "0.0.0+dev"
9
+
10
+ __all__ = ["__version__"]
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.0.1'
22
+ __version_tuple__ = version_tuple = (0, 0, 1)
23
+
24
+ __commit_id__ = commit_id = None
File without changes
@@ -0,0 +1,74 @@
1
+ # SPDX-License-Identifier: ISC
2
+ # Copyright: 2026 NiceBots.xyz
3
+ """Pydantic-backed JSON field for Tortoise ORM."""
4
+
5
+ from typing import Any, override
6
+
7
+ from pydantic import BaseModel
8
+ from tortoise.fields.data import JSONField
9
+ from tortoise.models import Model
10
+
11
+
12
+ class PydanticJSONField[T: BaseModel](JSONField[dict[str, object]]):
13
+ """JSONField that round-trips values through a Pydantic model.
14
+
15
+ On save, serializes ``BaseModel`` instances with ``model_dump(mode="json")``
16
+ before JSON encoding. On load, validates decoded data with the configured
17
+ Pydantic model, including:
18
+
19
+ - JSON strings or bytes (typical for SQLite).
20
+ - Plain dicts returned by PostgreSQL JSONB drivers such as asyncpg.
21
+
22
+ Examples:
23
+ >>> class MyModel(Model):
24
+ ... data: PydanticJSONField[MyPydanticModel] = PydanticJSONField(
25
+ ... MyPydanticModel, default=MyPydanticModel
26
+ ... )
27
+ """
28
+
29
+ def __init__(self, model_class: type[T], **kwargs: Any) -> None:
30
+ """Initialize the field.
31
+
32
+ Args:
33
+ model_class: Pydantic model class used for validation.
34
+ **kwargs: Additional arguments forwarded to ``JSONField``.
35
+ """
36
+ self._pydantic_model: type[T] = model_class
37
+ super().__init__(**kwargs)
38
+
39
+ @override
40
+ def to_python_value(self, value: Any) -> T | None: # pyright: ignore[reportExplicitAny, reportIncompatibleMethodOverride]
41
+ """Convert a database value to a Pydantic model instance.
42
+
43
+ Args:
44
+ value: Raw value from the database driver.
45
+
46
+ Returns:
47
+ A validated Pydantic model, or ``None`` when the value is null.
48
+ """
49
+ if value is None:
50
+ return None
51
+ if isinstance(value, self._pydantic_model):
52
+ return value
53
+ if isinstance(value, (str, bytes)):
54
+ raw = value if isinstance(value, str) else value.decode()
55
+ return self._pydantic_model.model_validate_json(raw)
56
+ return self._pydantic_model.model_validate(value)
57
+
58
+ @override
59
+ def to_db_value(self, value: Any, instance: type[Model] | Model) -> str | None: # pyright: ignore[reportExplicitAny]
60
+ """Convert a Python value to a JSON string for storage.
61
+
62
+ Args:
63
+ value: Model instance or JSON-serializable data.
64
+ instance: Tortoise model class or instance (unused).
65
+
66
+ Returns:
67
+ Encoded JSON string, or ``None`` when the value is null.
68
+ """
69
+ if isinstance(value, BaseModel):
70
+ value = value.model_dump(mode="json")
71
+ return super().to_db_value(value, instance)
72
+
73
+
74
+ __all__ = ["PydanticJSONField"]
@@ -0,0 +1,279 @@
1
+ # SPDX-License-Identifier: ISC
2
+ # Copyright: 2026 NiceBots.xyz
3
+ """S3-backed field for Tortoise ORM (via the ``obstore`` package)."""
4
+
5
+ import sys
6
+ from collections.abc import Awaitable, Callable, Mapping
7
+ from dataclasses import dataclass
8
+ from typing import TYPE_CHECKING, Any, override
9
+
10
+ from obstore.store import ObjectStore, S3Store
11
+ from tortoise import fields
12
+ from tortoise.exceptions import ConfigurationError
13
+ from tortoise.models import Model
14
+
15
+ if sys.version_info >= (3, 14):
16
+ from uuid import uuid7
17
+ else:
18
+ from uuid6 import uuid7
19
+
20
+ if TYPE_CHECKING:
21
+ from obstore.store import S3Config
22
+
23
+ _stores: dict[str, ObjectStore] = {}
24
+
25
+
26
+ @dataclass(frozen=True, slots=True, kw_only=True)
27
+ class S3Storage:
28
+ """Connection settings for one S3-compatible endpoint.
29
+
30
+ Attributes:
31
+ endpoint: Host and optional port, without scheme (e.g. ``"minio:9000"``).
32
+ bucket: Bucket holding the objects.
33
+ access_key: Access key ID.
34
+ secret_key: Secret access key.
35
+ region: Signing region, or ``None`` to let ``obstore`` resolve it.
36
+ ssl: Use ``https`` when ``True``, plain ``http`` when ``False``.
37
+ """
38
+
39
+ endpoint: str
40
+ bucket: str
41
+ access_key: str
42
+ secret_key: str
43
+ region: str | None = None
44
+ ssl: bool = True
45
+
46
+ def __post_init__(self) -> None:
47
+ """Validate the endpoint.
48
+
49
+ Raises:
50
+ ValueError: If the endpoint contains a scheme, which ``ssl`` controls.
51
+ """
52
+ if "://" in self.endpoint:
53
+ raise ValueError("S3Storage.endpoint must not contain a scheme, use `ssl` instead")
54
+
55
+ def to_store(self) -> S3Store:
56
+ """Build the ``obstore`` store for these settings.
57
+
58
+ Returns:
59
+ A configured ``S3Store``.
60
+ """
61
+ config: S3Config = {
62
+ "endpoint": f"{'https' if self.ssl else 'http'}://{self.endpoint}",
63
+ "access_key_id": self.access_key,
64
+ "secret_access_key": self.secret_key,
65
+ }
66
+ if self.region is not None:
67
+ config["region"] = self.region
68
+ return S3Store(self.bucket, config=config, client_options={"allow_http": not self.ssl})
69
+
70
+
71
+ def init_storages(storages: Mapping[str, S3Storage | ObjectStore]) -> None:
72
+ """Register the named storages used by ``S3Field``, replacing any previous ones.
73
+
74
+ Names are Tortoise app labels (the default storage of every model in that app)
75
+ or free names referenced through ``S3Field(storage=...)``.
76
+
77
+ Args:
78
+ storages: Storage settings, or ready ``obstore`` stores, by name.
79
+ """
80
+ _stores.clear()
81
+ _stores.update(
82
+ {name: value.to_store() if isinstance(value, S3Storage) else value for name, value in storages.items()}
83
+ )
84
+
85
+
86
+ def get_store(name: str) -> ObjectStore:
87
+ """Return a registered store.
88
+
89
+ Args:
90
+ name: Storage name.
91
+
92
+ Returns:
93
+ The registered store.
94
+
95
+ Raises:
96
+ ConfigurationError: If no storage is registered under ``name``.
97
+ """
98
+ try:
99
+ return _stores[name]
100
+ except KeyError:
101
+ raise ConfigurationError(f"S3 storage {name!r} is not initialized, see `init_storages`") from None
102
+
103
+
104
+ @dataclass(slots=True)
105
+ class S3Object:
106
+ """Handle on an object stored in S3, as loaded from an ``S3Field``.
107
+
108
+ An empty handle (``key is None``) maps to ``NULL`` and can still be written to.
109
+ Writing to an empty handle generates a key, so the model must be saved afterwards.
110
+
111
+ Attributes:
112
+ storage: Name of the storage holding the object.
113
+ key: Object key, or ``None`` when nothing is stored.
114
+ """
115
+
116
+ storage: str
117
+ key: str | None = None
118
+
119
+ @property
120
+ def is_empty(self) -> bool:
121
+ """Whether the handle has no key (no I/O performed)."""
122
+ return self.key is None
123
+
124
+ async def read(self) -> bytes | None:
125
+ """Download the object.
126
+
127
+ Returns:
128
+ The object content, or ``None`` when the handle is empty.
129
+ """
130
+ if self.key is None:
131
+ return None
132
+ result = await get_store(self.storage).get_async(self.key)
133
+ return bytes(await result.bytes_async())
134
+
135
+ async def write(self, data: bytes, *, key: str | None = None) -> str:
136
+ """Upload ``data``, generating a UUID7 key when the handle has none.
137
+
138
+ Args:
139
+ data: Content to store.
140
+ key: Explicit key to write to, replacing the current one.
141
+
142
+ Returns:
143
+ The key written to.
144
+ """
145
+ target = key or self.key or uuid7().hex
146
+ await get_store(self.storage).put_async(target, data)
147
+ self.key = target
148
+ return target
149
+
150
+ async def delete(self) -> None:
151
+ """Delete the object and empty the handle. Does nothing when already empty."""
152
+ if self.key is None:
153
+ return
154
+ await get_store(self.storage).delete_async(self.key)
155
+ self.key = None
156
+
157
+ async def exists(self) -> bool:
158
+ """Check whether the object exists in the storage.
159
+
160
+ Returns:
161
+ ``False`` when the handle is empty or the object is missing.
162
+ """
163
+ if self.key is None:
164
+ return False
165
+ try:
166
+ await get_store(self.storage).head_async(self.key)
167
+ except FileNotFoundError:
168
+ return False
169
+ return True
170
+
171
+ async def read_or(self, default: bytes) -> bytes:
172
+ """Download the object, falling back to ``default`` without writing it.
173
+
174
+ Args:
175
+ default: Value returned when the handle is empty or the object is missing.
176
+
177
+ Returns:
178
+ The object content, or ``default``.
179
+ """
180
+ try:
181
+ data = await self.read()
182
+ except FileNotFoundError:
183
+ return default
184
+ return default if data is None else data
185
+
186
+ async def get_or_write(self, value: bytes | Callable[[], Awaitable[bytes]]) -> bytes:
187
+ """Download the object, writing ``value`` first when it is empty or missing.
188
+
189
+ Args:
190
+ value: Content to store, or an async factory only awaited when needed.
191
+
192
+ Returns:
193
+ The stored content.
194
+ """
195
+ try:
196
+ data = await self.read()
197
+ except FileNotFoundError:
198
+ data = None
199
+ if data is not None:
200
+ return data
201
+ data = value if isinstance(value, bytes) else await value()
202
+ await self.write(data)
203
+ return data
204
+
205
+
206
+ class S3Field(fields.Field[S3Object]):
207
+ """Field storing an S3 object key, loaded as an ``S3Object`` handle.
208
+
209
+ The storage is ``storage`` when given, otherwise the model's Tortoise app label.
210
+ ``NULL`` columns and new instances load as empty, writable handles.
211
+
212
+ Examples:
213
+ >>> class Attachment(Model):
214
+ ... file = S3Field(null=True)
215
+ ... thumb = S3Field(storage="cdn", null=True)
216
+ """
217
+
218
+ SQL_TYPE = "VARCHAR(1024)"
219
+ field_type = S3Object
220
+
221
+ def __init__(self, storage: str | None = None, **kwargs: Any) -> None: # pyright: ignore[reportExplicitAny]
222
+ """Initialize the field.
223
+
224
+ Args:
225
+ storage: Storage name overriding the model's app label.
226
+ **kwargs: Tortoise field options.
227
+ """
228
+ self._storage: str | None = storage
229
+ kwargs.setdefault("default", self._empty)
230
+ super().__init__(**kwargs)
231
+
232
+ @property
233
+ def storage_name(self) -> str:
234
+ """Storage name resolved from the override or the model's app label.
235
+
236
+ Raises:
237
+ ConfigurationError: If neither is available (Tortoise not initialized).
238
+ """
239
+ if self._storage is not None:
240
+ return self._storage
241
+ app = self.model._meta.app if self.model else None # noqa: SLF001 # pyright: ignore[reportPrivateUsage, reportUnnecessaryComparison]
242
+ if app is None:
243
+ raise ConfigurationError(f"{self.model_field_name!r} has no storage, pass one or initialize Tortoise")
244
+ return app
245
+
246
+ def _empty(self) -> S3Object:
247
+ return S3Object(self.storage_name)
248
+
249
+ @override
250
+ def to_python_value(self, value: Any) -> S3Object: # pyright: ignore[reportExplicitAny]
251
+ """Convert a database value to an ``S3Object`` handle.
252
+
253
+ Args:
254
+ value: Stored key, handle, or ``None``.
255
+
256
+ Returns:
257
+ A handle, empty when the value is ``None``.
258
+ """
259
+ if isinstance(value, S3Object):
260
+ return value
261
+ return S3Object(self.storage_name, None if value is None else str(value))
262
+
263
+ @override
264
+ def to_db_value(self, value: Any, instance: type[Model] | Model) -> str | None: # pyright: ignore[reportExplicitAny]
265
+ """Convert a handle or key to its database representation.
266
+
267
+ Args:
268
+ value: Handle, raw key, or ``None``.
269
+ instance: Tortoise model class or instance (unused).
270
+
271
+ Returns:
272
+ The object key, or ``None`` when empty.
273
+ """
274
+ if isinstance(value, S3Object):
275
+ return value.key
276
+ return None if value is None else str(value)
277
+
278
+
279
+ __all__ = ["S3Field", "S3Object", "S3Storage", "get_store", "init_storages"]
@@ -0,0 +1,74 @@
1
+ # SPDX-License-Identifier: ISC
2
+ # Copyright: 2026 NiceBots.xyz
3
+ """UUID v6+ field for Tortoise ORM (via the ``uuid6`` package)."""
4
+
5
+ from typing import Any, override
6
+
7
+ from tortoise import fields
8
+ from tortoise.models import Model
9
+ from uuid6 import UUID, uuid7
10
+
11
+
12
+ class FutureUUIDField(fields.Field[UUID], UUID): # pyright: ignore[reportUnsafeMultipleInheritance]
13
+ """UUID field compatible with UUID v6+ and the ``uuid6`` package.
14
+
15
+ Stores UUIDs as ``CHAR(36)`` on most backends and as native ``UUID`` on
16
+ PostgreSQL. When used as a primary key without an explicit ``default``,
17
+ assigns UUID7 via ``uuid6.uuid7``.
18
+
19
+ Examples:
20
+ >>> class Item(Model):
21
+ ... id: FutureUUIDField = FutureUUIDField(primary_key=True)
22
+ ... class Meta:
23
+ ... table = "items"
24
+ """
25
+
26
+ SQL_TYPE = "CHAR(36)"
27
+
28
+ class _db_postgres:
29
+ SQL_TYPE = "UUID"
30
+
31
+ def __init__(self, **kwargs: Any) -> None: # pyright: ignore[reportExplicitAny]
32
+ """Initialize the field.
33
+
34
+ Args:
35
+ **kwargs: Tortoise field options. Primary keys without ``default``
36
+ receive ``uuid7`` automatically.
37
+ """
38
+ if kwargs.pop("pk", False):
39
+ kwargs["primary_key"] = True
40
+ if kwargs.get("primary_key") and "default" not in kwargs:
41
+ kwargs["default"] = uuid7
42
+ super().__init__(**kwargs)
43
+
44
+ @override
45
+ def to_db_value(self, value: Any, instance: type[Model] | Model) -> str | None: # pyright: ignore[reportExplicitAny]
46
+ """Convert a UUID to its string database representation.
47
+
48
+ Args:
49
+ value: UUID value or other supported input.
50
+ instance: Tortoise model class or instance (unused).
51
+
52
+ Returns:
53
+ String UUID, or ``None`` when the value is falsy.
54
+ """
55
+ return value and str(value)
56
+
57
+ @override
58
+ def to_python_value(self, value: Any) -> UUID | None: # pyright: ignore[reportExplicitAny]
59
+ """Convert a database value to a ``uuid6.UUID`` instance.
60
+
61
+ Args:
62
+ value: Raw value from the database driver.
63
+
64
+ Returns:
65
+ Parsed UUID, or ``None`` when the value is null.
66
+ """
67
+ if value is None:
68
+ return None
69
+ if isinstance(value, UUID):
70
+ return value
71
+ return UUID(str(value))
72
+
73
+
74
+ __all__ = ["FutureUUIDField"]
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.5
2
+ Name: tortoise-extensions
3
+ Version: 0.0.1
4
+ Summary: Extension field types for Tortoise ORM
5
+ Project-URL: Documentation, https://docs.nicebots.xyz/tortoise-extensions/
6
+ Project-URL: Repository, https://github.com/nicebots-xyz/tortoise-extensions
7
+ Project-URL: Issues, https://github.com/nicebots-xyz/tortoise-extensions/issues
8
+ Project-URL: Source Archive, https://github.com/nicebots-xyz/tortoise-extensions/archive/be1848c93a01c69d6ccce132b5820ad41b5337ee.zip
9
+ Author-email: Paillat-dev <me@paillat.dev>
10
+ License-Expression: ISC
11
+ License-File: LICENSE
12
+ Keywords: fields,orm,pydantic,s3,tortoise,uuid6
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: ISC License (ISCL)
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: <3.15,>=3.12
22
+ Requires-Dist: tortoise-orm<1.0.0,>=0.25.4
23
+ Provides-Extra: pydantic
24
+ Requires-Dist: pydantic~=2.0; extra == 'pydantic'
25
+ Provides-Extra: s3
26
+ Requires-Dist: obstore~=0.12.1; extra == 's3'
27
+ Requires-Dist: uuid6>=2024.0.0; (python_version < '3.14') and extra == 's3'
28
+ Provides-Extra: uuid6
29
+ Requires-Dist: uuid6>=2024.0.0; extra == 'uuid6'
30
+ Description-Content-Type: text/markdown
31
+
32
+ <!--
33
+ SPDX-License-Identifier: ISC
34
+ Copyright: 2026 NiceBots.xyz
35
+ -->
36
+ # tortoise-extensions
37
+
38
+ Optional extension field types for [Tortoise ORM](https://tortoise.github.io/). This package targets **Tortoise ORM 0.25.4 and later, but before 1.0** — Tortoise 1.x is not supported yet.
39
+
40
+ ## Install
41
+
42
+ Base package (requires Tortoise ORM in the supported range):
43
+
44
+ ```bash
45
+ pip install tortoise-extensions
46
+ ```
47
+
48
+ Optional extras:
49
+
50
+ ```bash
51
+ pip install tortoise-extensions[pydantic]
52
+ pip install tortoise-extensions[uuid6]
53
+ pip install tortoise-extensions[s3]
54
+ pip install tortoise-extensions[pydantic,uuid6,s3]
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Pydantic JSON (`pydantic` extra)
60
+
61
+ ```python
62
+ from pydantic import BaseModel
63
+ from tortoise.models import Model
64
+
65
+ from tortoise_extensions.pydantic import PydanticJSONField
66
+
67
+
68
+ class Settings(BaseModel):
69
+ theme: str = "dark"
70
+
71
+
72
+ class AppConfig(Model):
73
+ settings: PydanticJSONField[Settings] = PydanticJSONField(Settings, default=Settings)
74
+
75
+ class Meta:
76
+ table = "app_configs"
77
+ ```
78
+
79
+ `PydanticJSONField` round-trips through your Pydantic model: it accepts dicts from PostgreSQL JSONB, JSON strings from SQLite, and serializes models with `model_dump(mode="json")` on save.
80
+
81
+ ### UUID v6+ (`uuid6` extra)
82
+
83
+ ```python
84
+ from tortoise.models import Model
85
+
86
+ from tortoise_extensions.uuid6 import FutureUUIDField
87
+
88
+
89
+ class Item(Model):
90
+ id: FutureUUIDField = FutureUUIDField(primary_key=True)
91
+
92
+ class Meta:
93
+ table = "items"
94
+ ```
95
+
96
+ Primary keys without an explicit `default` receive a UUID7 from the [`uuid6`](https://pypi.org/project/uuid6/) package. PostgreSQL uses the native `UUID` column type; other backends store `CHAR(36)`.
97
+
98
+ ### S3 objects (`s3` extra)
99
+
100
+ ```python
101
+ from tortoise.models import Model
102
+
103
+ from tortoise_extensions.s3 import S3Field
104
+
105
+
106
+ class Attachment(Model):
107
+ file = S3Field(null=True)
108
+
109
+ class Meta:
110
+ table = "attachments"
111
+ ```
112
+
113
+ The column stores an object key and loads as an `S3Object` handle (`read`, `write`, `delete`, `get_or_write`, ...) backed by [`obstore`](https://pypi.org/project/obstore/). Storages are registered per Tortoise app with `init_storages`, each with its own `ssl` setting. See the [usage docs](https://docs.nicebots.xyz/tortoise-extensions/usage/) for details.
114
+
115
+ ## Contributing a new extension
116
+
117
+ 1. **Open an issue first** — describe the field behavior, SQL types per backend, which Tortoise versions you need, and the proposed optional-extra name (e.g. `pip install tortoise-extensions[myextra]`).
118
+ 2. After maintainers agree on the design, open a PR that includes:
119
+ - Implementation in `src/tortoise_extensions/<module>.py`
120
+ - Tests under `tests/`
121
+ - An entry in `[project.optional-dependencies]` if the field needs third-party packages
122
+ - README and docs updates for the new field
123
+ - ISC SPDX headers on new files (`licensor-config.yaml` applies)
124
+ 3. Run `pdm run quality` locally before pushing.
125
+
126
+ ## Documentation
127
+
128
+ Published at [docs.nicebots.xyz/tortoise-extensions](https://docs.nicebots.xyz/tortoise-extensions/).
129
+
130
+ ```bash
131
+ pdm install -G docs
132
+ pdm run docs:build
133
+ pdm run docs:preview
134
+ pdm run docs:dev
135
+ ```
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ pdm install -G dev -G test -G docs
141
+ pdm run quality
142
+ ```
143
+
144
+ Public APIs use [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings); `ruff` enforces this via `convention = "google"`.
145
+
146
+ ## License
147
+
148
+ ISC.
@@ -0,0 +1,10 @@
1
+ tortoise_extensions/__init__.py,sha256=53rMk6fOZ73lLLyfN4mw3jjKCgwJ-RG-2vr9KX4sfo0,248
2
+ tortoise_extensions/_version.py,sha256=8OsTLsIVB9D0HdPTmt5rVwyVUBe9xTVGkRslXicxzkM,520
3
+ tortoise_extensions/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ tortoise_extensions/pydantic.py,sha256=lqazG4Ek91KmSzhg-hpMOOXPmO2-wOxJkKe2YsqJC4g,2643
5
+ tortoise_extensions/s3.py,sha256=R6-4bmtGHf4jKBTweJ-5MVCMVoELbeFht6FqvCQGMfs,9001
6
+ tortoise_extensions/uuid6.py,sha256=LqoCktYxjvxhtpbJ3mwd40PbxNkKCJJ1I_pfM5QN-Pg,2358
7
+ tortoise_extensions-0.0.1.dist-info/METADATA,sha256=NJsKaLKkZRm8bjpoyEU-VRn4uVKmlrVtmASFndrgi8E,4759
8
+ tortoise_extensions-0.0.1.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
9
+ tortoise_extensions-0.0.1.dist-info/licenses/LICENSE,sha256=lh4wKPdjQxcYj8MlbyIUjEoaTk6SkcKOiVpNJsJnMl0,743
10
+ tortoise_extensions-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 NiceBots.xyz
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14
+ OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.