mongo-entity-orm 0.1.0__tar.gz → 0.1.1__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.
- mongo_entity_orm-0.1.1/PKG-INFO +94 -0
- mongo_entity_orm-0.1.1/README.md +69 -0
- {mongo_entity_orm-0.1.0 → mongo_entity_orm-0.1.1}/pyproject.toml +1 -1
- {mongo_entity_orm-0.1.0 → mongo_entity_orm-0.1.1}/src/mongo_orm/core.py +46 -0
- mongo_entity_orm-0.1.1/src/mongo_orm/utils.py +137 -0
- mongo_entity_orm-0.1.0/PKG-INFO +0 -55
- mongo_entity_orm-0.1.0/README.md +0 -30
- {mongo_entity_orm-0.1.0 → mongo_entity_orm-0.1.1}/LICENSE +0 -0
- {mongo_entity_orm-0.1.0 → mongo_entity_orm-0.1.1}/src/mongo_orm/__init__.py +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: mongo-entity-orm
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: A MongoDB ORM for Python
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: mongodb,orm,mongo,async
|
|
7
|
+
Author: Juraj Bezdek
|
|
8
|
+
Author-email: juraj.bezdek@gmail.com
|
|
9
|
+
Requires-Python: >=3.9,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Requires-Dist: motor (>=3.7.1,<4.0.0)
|
|
20
|
+
Requires-Dist: pymongo (>=4.13.2,<5.0.0)
|
|
21
|
+
Project-URL: Documentation, https://github.com/ju-bezdek/mongo#readme
|
|
22
|
+
Project-URL: Homepage, https://github.com/ju-bezdek/mongo-orm
|
|
23
|
+
Project-URL: Repository, https://github.com/ju-bezdek/mongo
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# mongo-orm
|
|
27
|
+
|
|
28
|
+
A MongoDB ORM for Python
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Defining an Entity
|
|
33
|
+
|
|
34
|
+
Use the `@entity` decorator to map a class to a MongoDB collection. Your class should inherit from `BaseEntity`.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from mongo_orm import BaseEntity, entity
|
|
38
|
+
|
|
39
|
+
@entity("users")
|
|
40
|
+
class User(BaseEntity):
|
|
41
|
+
name: str
|
|
42
|
+
email: str
|
|
43
|
+
|
|
44
|
+
# Define indexes
|
|
45
|
+
__indexes__ = [
|
|
46
|
+
{"keys": [("email", 1)], "unique": True},
|
|
47
|
+
{"keys": [("name", 1)]}
|
|
48
|
+
]
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Index Management
|
|
52
|
+
|
|
53
|
+
The ORM can automatically manage your MongoDB indexes based on the `__indexes__` attribute. This behavior is controlled by the `MONGODB_INDEX_AUTOAPPLY` environment variable.
|
|
54
|
+
|
|
55
|
+
#### Configuration (`MONGODB_INDEX_AUTOAPPLY`)
|
|
56
|
+
|
|
57
|
+
- `never` (default): No automatic index management.
|
|
58
|
+
- `always`: Checks and applies indexes every time an entity class is initialized (on startup).
|
|
59
|
+
- `auto-lock`: Checks and applies indexes once. After a successful check, it writes a hash of the index to `mongo-orm.lock`. Subsequent startups will skip the check if the hash is present in the lock file.
|
|
60
|
+
|
|
61
|
+
#### Manual Index Application
|
|
62
|
+
|
|
63
|
+
You can also trigger index application manually for all registered entities:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from mongo_orm.utils import apply_all_indexes
|
|
67
|
+
|
|
68
|
+
# Apply indexes using the current environment configuration
|
|
69
|
+
apply_all_indexes()
|
|
70
|
+
|
|
71
|
+
# Or force a specific mode
|
|
72
|
+
apply_all_indexes(mode="always")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Development
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Install dependencies
|
|
79
|
+
poetry install
|
|
80
|
+
|
|
81
|
+
# Run tests
|
|
82
|
+
poetry run pytest
|
|
83
|
+
|
|
84
|
+
# Format code
|
|
85
|
+
poetry run black .
|
|
86
|
+
poetry run isort .
|
|
87
|
+
|
|
88
|
+
# Type checking
|
|
89
|
+
poetry run mypy src/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# mongo-orm
|
|
2
|
+
|
|
3
|
+
A MongoDB ORM for Python
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
### Defining an Entity
|
|
8
|
+
|
|
9
|
+
Use the `@entity` decorator to map a class to a MongoDB collection. Your class should inherit from `BaseEntity`.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from mongo_orm import BaseEntity, entity
|
|
13
|
+
|
|
14
|
+
@entity("users")
|
|
15
|
+
class User(BaseEntity):
|
|
16
|
+
name: str
|
|
17
|
+
email: str
|
|
18
|
+
|
|
19
|
+
# Define indexes
|
|
20
|
+
__indexes__ = [
|
|
21
|
+
{"keys": [("email", 1)], "unique": True},
|
|
22
|
+
{"keys": [("name", 1)]}
|
|
23
|
+
]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Index Management
|
|
27
|
+
|
|
28
|
+
The ORM can automatically manage your MongoDB indexes based on the `__indexes__` attribute. This behavior is controlled by the `MONGODB_INDEX_AUTOAPPLY` environment variable.
|
|
29
|
+
|
|
30
|
+
#### Configuration (`MONGODB_INDEX_AUTOAPPLY`)
|
|
31
|
+
|
|
32
|
+
- `never` (default): No automatic index management.
|
|
33
|
+
- `always`: Checks and applies indexes every time an entity class is initialized (on startup).
|
|
34
|
+
- `auto-lock`: Checks and applies indexes once. After a successful check, it writes a hash of the index to `mongo-orm.lock`. Subsequent startups will skip the check if the hash is present in the lock file.
|
|
35
|
+
|
|
36
|
+
#### Manual Index Application
|
|
37
|
+
|
|
38
|
+
You can also trigger index application manually for all registered entities:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from mongo_orm.utils import apply_all_indexes
|
|
42
|
+
|
|
43
|
+
# Apply indexes using the current environment configuration
|
|
44
|
+
apply_all_indexes()
|
|
45
|
+
|
|
46
|
+
# Or force a specific mode
|
|
47
|
+
apply_all_indexes(mode="always")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Install dependencies
|
|
54
|
+
poetry install
|
|
55
|
+
|
|
56
|
+
# Run tests
|
|
57
|
+
poetry run pytest
|
|
58
|
+
|
|
59
|
+
# Format code
|
|
60
|
+
poetry run black .
|
|
61
|
+
poetry run isort .
|
|
62
|
+
|
|
63
|
+
# Type checking
|
|
64
|
+
poetry run mypy src/
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
|
@@ -6,15 +6,20 @@ import logging
|
|
|
6
6
|
from collections import deque
|
|
7
7
|
import os
|
|
8
8
|
from typing import (
|
|
9
|
+
Any,
|
|
9
10
|
AsyncIterator,
|
|
10
11
|
Awaitable,
|
|
11
12
|
Callable,
|
|
13
|
+
Dict,
|
|
12
14
|
Generator,
|
|
13
15
|
Iterable,
|
|
14
16
|
List,
|
|
17
|
+
Optional,
|
|
18
|
+
Tuple,
|
|
15
19
|
Type,
|
|
16
20
|
TypeVar,
|
|
17
21
|
TypedDict,
|
|
22
|
+
Union,
|
|
18
23
|
)
|
|
19
24
|
from uuid import NAMESPACE_URL, uuid4, uuid5
|
|
20
25
|
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection
|
|
@@ -34,6 +39,10 @@ class Config:
|
|
|
34
39
|
def MONGO_DATABASE_NAME():
|
|
35
40
|
return os.environ.get("MONGO_DATABASE_NAME", None)
|
|
36
41
|
|
|
42
|
+
@staticmethod
|
|
43
|
+
def MONGODB_INDEX_AUTOAPPLY():
|
|
44
|
+
return os.environ.get("MONGODB_INDEX_AUTOAPPLY", "never")
|
|
45
|
+
|
|
37
46
|
|
|
38
47
|
def encode_tenant_id(tenant_id, id):
|
|
39
48
|
return str(uuid5(NAMESPACE_URL, f"{id}/{tenant_id}"))
|
|
@@ -45,6 +54,7 @@ class SaveResult(TypedDict):
|
|
|
45
54
|
|
|
46
55
|
|
|
47
56
|
class Repository:
|
|
57
|
+
all_entities: List[Type["BaseEntity"]] = []
|
|
48
58
|
|
|
49
59
|
def __init__(self, mongo_db: MongoDatabase = None) -> None:
|
|
50
60
|
if mongo_db:
|
|
@@ -68,6 +78,33 @@ REPOSITORY = Repository()
|
|
|
68
78
|
|
|
69
79
|
|
|
70
80
|
T = TypeVar("T", bound="BaseEntity")
|
|
81
|
+
Keys = Union[
|
|
82
|
+
str,
|
|
83
|
+
List[Union[str, Tuple[str, int]]],
|
|
84
|
+
Dict[str, int],
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class IndexSpec(TypedDict, total=False):
|
|
89
|
+
"""
|
|
90
|
+
Typed dict describing a MongoDB index.
|
|
91
|
+
|
|
92
|
+
Examples:
|
|
93
|
+
{"keys": "email", "unique": True}
|
|
94
|
+
{"keys": [("tenant_id", 1), ("created_at", -1)], "name": "tenant_created_idx"}
|
|
95
|
+
{"keys": {"email": 1}, "unique": True}
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
keys: Keys
|
|
99
|
+
# Optional index options
|
|
100
|
+
unique: bool
|
|
101
|
+
sparse: bool
|
|
102
|
+
background: bool
|
|
103
|
+
name: str
|
|
104
|
+
expireAfterSeconds: int
|
|
105
|
+
expire_after_seconds: int # alias
|
|
106
|
+
partialFilterExpression: Dict[str, Any]
|
|
107
|
+
weights: Dict[str, int]
|
|
71
108
|
|
|
72
109
|
|
|
73
110
|
class BaseEntity(BaseModel):
|
|
@@ -81,6 +118,8 @@ class BaseEntity(BaseModel):
|
|
|
81
118
|
_loaded_at: datetime.datetime | None = PrivateAttr(None)
|
|
82
119
|
_tracked_fields_state: dict | None = PrivateAttr(None)
|
|
83
120
|
|
|
121
|
+
__indexes__: Optional[List[IndexSpec]] = None
|
|
122
|
+
|
|
84
123
|
def __init__(self, **data) -> None:
|
|
85
124
|
if not hasattr(self.__class__, "collection"):
|
|
86
125
|
raise Exception(
|
|
@@ -557,6 +596,13 @@ def entity(
|
|
|
557
596
|
|
|
558
597
|
cls.version_field = version_field
|
|
559
598
|
|
|
599
|
+
if cls not in REPOSITORY.all_entities:
|
|
600
|
+
REPOSITORY.all_entities.append(cls)
|
|
601
|
+
|
|
602
|
+
from .utils import apply_indexes
|
|
603
|
+
|
|
604
|
+
apply_indexes(cls)
|
|
605
|
+
|
|
560
606
|
return cls
|
|
561
607
|
|
|
562
608
|
return decorator
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
from typing import TYPE_CHECKING, List, Literal, Type
|
|
5
|
+
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorCollection
|
|
6
|
+
from pymongo.collection import Collection
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from .core import BaseEntity, IndexSpec
|
|
10
|
+
|
|
11
|
+
LOCK_FILE = "mongo-orm.lock"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _get_index_hash(collection_name: str, spec: "IndexSpec") -> str:
|
|
15
|
+
"""Generate a stable hash for a collection + index specification."""
|
|
16
|
+
keys = spec.get("keys") or spec.get("key")
|
|
17
|
+
# Normalize keys to a stable tuple representation
|
|
18
|
+
if isinstance(keys, dict):
|
|
19
|
+
# Keep order as provided in dict
|
|
20
|
+
norm_keys = tuple((str(k), float(v)) for k, v in keys.items())
|
|
21
|
+
elif isinstance(keys, list):
|
|
22
|
+
norm_keys = tuple((str(k), float(v)) for k, v in keys)
|
|
23
|
+
else:
|
|
24
|
+
norm_keys = ((str(keys), 1.0),)
|
|
25
|
+
|
|
26
|
+
# Create a stable representation of the spec including important options
|
|
27
|
+
relevant_parts = {
|
|
28
|
+
"collection": collection_name,
|
|
29
|
+
"keys": norm_keys,
|
|
30
|
+
"unique": spec.get("unique", False),
|
|
31
|
+
"sparse": spec.get("sparse", False),
|
|
32
|
+
"partialFilterExpression": spec.get("partialFilterExpression"),
|
|
33
|
+
"expireAfterSeconds": spec.get("expireAfterSeconds")
|
|
34
|
+
or spec.get("expire_after_seconds"),
|
|
35
|
+
}
|
|
36
|
+
# Sort top-level keys of relevant_parts to ensure stable string representation
|
|
37
|
+
spec_str = str(sorted(relevant_parts.items(), key=lambda x: x[0]))
|
|
38
|
+
return hashlib.sha256(spec_str.encode()).hexdigest()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def index_exists(collection: Collection, spec: "IndexSpec") -> bool:
|
|
42
|
+
"""Return True if an equivalent index already exists in `collection` (sync)."""
|
|
43
|
+
|
|
44
|
+
# Normalize target keys to a list of tuples
|
|
45
|
+
keys = spec.get("keys") or spec.get("key")
|
|
46
|
+
if isinstance(keys, dict):
|
|
47
|
+
target_key = list(keys.items())
|
|
48
|
+
elif isinstance(keys, list):
|
|
49
|
+
target_key = keys
|
|
50
|
+
else:
|
|
51
|
+
target_key = [(keys, 1)]
|
|
52
|
+
|
|
53
|
+
# Normalize values to floats for comparison
|
|
54
|
+
target_key = [(str(k), float(v)) for k, v in target_key]
|
|
55
|
+
|
|
56
|
+
existing_indexes = collection.index_information()
|
|
57
|
+
|
|
58
|
+
for name, info in existing_indexes.items():
|
|
59
|
+
# info['key'] can be a list of tuples or a SON/dict object
|
|
60
|
+
raw_existing_key = info["key"]
|
|
61
|
+
if hasattr(raw_existing_key, "items"):
|
|
62
|
+
existing_key = [(str(k), float(v)) for k, v in raw_existing_key.items()]
|
|
63
|
+
else:
|
|
64
|
+
existing_key = [(str(k), float(v)) for k, v in raw_existing_key]
|
|
65
|
+
|
|
66
|
+
if existing_key == target_key:
|
|
67
|
+
return True
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def apply_indexes(cls: Type["BaseEntity"], mode: Literal["auto-lock", "always"] = None):
|
|
72
|
+
"""Check and apply indexes for a specific entity class."""
|
|
73
|
+
from .core import Config
|
|
74
|
+
|
|
75
|
+
mode = mode or Config.MONGODB_INDEX_AUTOAPPLY()
|
|
76
|
+
if not mode or mode == "never":
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
indexes = getattr(cls, "__indexes__", None)
|
|
80
|
+
if not indexes:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
collection = cls.get_collection()
|
|
84
|
+
collection_name = cls.__collection_name__
|
|
85
|
+
|
|
86
|
+
applied_hashes = set()
|
|
87
|
+
if mode == "auto-lock" and os.path.exists(LOCK_FILE):
|
|
88
|
+
with open(LOCK_FILE, "r") as f:
|
|
89
|
+
applied_hashes = {line.strip() for line in f if line.strip()}
|
|
90
|
+
|
|
91
|
+
new_hashes = []
|
|
92
|
+
|
|
93
|
+
for spec in indexes:
|
|
94
|
+
idx_hash = _get_index_hash(collection_name, spec)
|
|
95
|
+
|
|
96
|
+
if mode == "auto-lock" and idx_hash in applied_hashes:
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
if not index_exists(collection, spec):
|
|
100
|
+
# Create index
|
|
101
|
+
keys = spec.get("keys") or spec.get("key")
|
|
102
|
+
options = {
|
|
103
|
+
k: v
|
|
104
|
+
for k, v in spec.items()
|
|
105
|
+
if k not in ("keys", "key", "expire_after_seconds")
|
|
106
|
+
}
|
|
107
|
+
if "expire_after_seconds" in spec:
|
|
108
|
+
options["expireAfterSeconds"] = spec["expire_after_seconds"]
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
collection.create_index(keys, **options)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
logging.error(
|
|
114
|
+
f"Failed to create index {keys} on {collection_name}: {e}"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if mode == "auto-lock":
|
|
118
|
+
new_hashes.append(idx_hash)
|
|
119
|
+
|
|
120
|
+
if new_hashes:
|
|
121
|
+
with open(LOCK_FILE, "a") as f:
|
|
122
|
+
for h in new_hashes:
|
|
123
|
+
f.write(f"{h}\n")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def apply_all_indexes(mode: str = None):
|
|
127
|
+
"""Apply indexes for all registered entity classes."""
|
|
128
|
+
from .core import Config, Repository
|
|
129
|
+
|
|
130
|
+
if mode is None:
|
|
131
|
+
mode = Config.MONGODB_INDEX_AUTOAPPLY()
|
|
132
|
+
|
|
133
|
+
if not mode or mode == "never":
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
for cls in Repository.all_entities:
|
|
137
|
+
apply_indexes(cls, mode)
|
mongo_entity_orm-0.1.0/PKG-INFO
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: mongo-entity-orm
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: A MongoDB ORM for Python
|
|
5
|
-
License: MIT
|
|
6
|
-
Keywords: mongodb,orm,mongo,async
|
|
7
|
-
Author: Juraj Bezdek
|
|
8
|
-
Author-email: juraj.bezdek@gmail.com
|
|
9
|
-
Requires-Python: >=3.9,<4.0
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: OS Independent
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Requires-Dist: motor (>=3.7.1,<4.0.0)
|
|
20
|
-
Requires-Dist: pymongo (>=4.13.2,<5.0.0)
|
|
21
|
-
Project-URL: Documentation, https://github.com/ju-bezdek/mongo#readme
|
|
22
|
-
Project-URL: Homepage, https://github.com/ju-bezdek/mongo-orm
|
|
23
|
-
Project-URL: Repository, https://github.com/ju-bezdek/mongo
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-
# mongo-orm
|
|
27
|
-
|
|
28
|
-
A MongoDB ORM for Python
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
pip install mongo-orm
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Development
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
# Install dependencies
|
|
40
|
-
poetry install
|
|
41
|
-
|
|
42
|
-
# Run tests
|
|
43
|
-
poetry run pytest
|
|
44
|
-
|
|
45
|
-
# Format code
|
|
46
|
-
poetry run black .
|
|
47
|
-
poetry run isort .
|
|
48
|
-
|
|
49
|
-
# Type checking
|
|
50
|
-
poetry run mypy src/
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## License
|
|
54
|
-
|
|
55
|
-
MIT
|
mongo_entity_orm-0.1.0/README.md
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# mongo-orm
|
|
2
|
-
|
|
3
|
-
A MongoDB ORM for Python
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pip install mongo-orm
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Development
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
# Install dependencies
|
|
15
|
-
poetry install
|
|
16
|
-
|
|
17
|
-
# Run tests
|
|
18
|
-
poetry run pytest
|
|
19
|
-
|
|
20
|
-
# Format code
|
|
21
|
-
poetry run black .
|
|
22
|
-
poetry run isort .
|
|
23
|
-
|
|
24
|
-
# Type checking
|
|
25
|
-
poetry run mypy src/
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## License
|
|
29
|
-
|
|
30
|
-
MIT
|
|
File without changes
|
|
File without changes
|