fastapi-sdk 0.1.0__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,109 @@
|
|
|
1
|
+
"""Controller module for crud operations."""
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
from typing import List, Optional, Type
|
|
5
|
+
|
|
6
|
+
from odmantic import AIOEngine, Model
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
|
|
9
|
+
from fastapi_sdk.utils.schema import datetime_now_sec
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Controller:
|
|
13
|
+
"""Base controller class."""
|
|
14
|
+
|
|
15
|
+
model: Type[Model]
|
|
16
|
+
schema_create: Type[BaseModel]
|
|
17
|
+
schema_update: Type[BaseModel]
|
|
18
|
+
n_per_page: int = 10
|
|
19
|
+
|
|
20
|
+
def __init__(self, db_engine: AIOEngine):
|
|
21
|
+
"""Initialize the controller."""
|
|
22
|
+
self.db_engine = db_engine
|
|
23
|
+
|
|
24
|
+
async def _create(self, **kwargs) -> BaseModel:
|
|
25
|
+
"""Create a new model."""
|
|
26
|
+
data = self.schema_create(**kwargs)
|
|
27
|
+
model = self.model(**data.model_dump())
|
|
28
|
+
return await self.db_engine.save(model)
|
|
29
|
+
|
|
30
|
+
async def _update(self, uuid: str, data: dict) -> BaseModel:
|
|
31
|
+
"""Update a model."""
|
|
32
|
+
model = await self._get(uuid)
|
|
33
|
+
data = self.schema_update(**data)
|
|
34
|
+
if model:
|
|
35
|
+
# Update the fields submitted
|
|
36
|
+
for field in data.model_dump(exclude_unset=True):
|
|
37
|
+
setattr(model, field, data.model_dump()[field])
|
|
38
|
+
model.updated_at = datetime_now_sec()
|
|
39
|
+
return await self.db_engine.save(model)
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
async def _get(self, uuid: str) -> BaseModel:
|
|
43
|
+
"""Get a model."""
|
|
44
|
+
return await self.db_engine.find_one(
|
|
45
|
+
self.model, self.model.uuid == uuid, self.model.deleted == False
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
async def _delete(self, uuid: str) -> BaseModel:
|
|
49
|
+
"""Delete a model."""
|
|
50
|
+
model = await self._get(uuid)
|
|
51
|
+
if model:
|
|
52
|
+
model.deleted = True
|
|
53
|
+
return await self.db_engine.save(model)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
async def _list(
|
|
57
|
+
self,
|
|
58
|
+
page: int = 0,
|
|
59
|
+
query: Optional[List[dict]] = None,
|
|
60
|
+
order_by: Optional[dict] = None,
|
|
61
|
+
) -> List[BaseModel]:
|
|
62
|
+
"""List models."""
|
|
63
|
+
# Get the collection
|
|
64
|
+
collection_name = self.model.model_config[
|
|
65
|
+
"collection"
|
|
66
|
+
] or self.model.__name__.lower().replace("model", "")
|
|
67
|
+
_collection = self.db_engine.database[collection_name]
|
|
68
|
+
|
|
69
|
+
# Create a pipeline for aggregation
|
|
70
|
+
_pipeline = []
|
|
71
|
+
|
|
72
|
+
# Filter out deleted models by default
|
|
73
|
+
# Example query: [{"due_date": {"$gte": start_date}}]
|
|
74
|
+
_query = {"deleted": False}
|
|
75
|
+
if query:
|
|
76
|
+
for q in query:
|
|
77
|
+
_query.update(q)
|
|
78
|
+
|
|
79
|
+
# Sorting, default by created_at
|
|
80
|
+
# Order by example: {"name": -1}, 1 ascending, -1 descending
|
|
81
|
+
_sort = order_by if order_by else {"created_at": -1}
|
|
82
|
+
|
|
83
|
+
# Add the pipeline stages
|
|
84
|
+
_pipeline.append({"$match": _query})
|
|
85
|
+
_pipeline.append({"$sort": _sort})
|
|
86
|
+
|
|
87
|
+
# Add pagination data
|
|
88
|
+
_pipeline.append({"$skip": (page - 1) * self.n_per_page if page > 0 else 0})
|
|
89
|
+
_pipeline.append({"$limit": self.n_per_page})
|
|
90
|
+
|
|
91
|
+
# Execute the aggregation
|
|
92
|
+
items = await _collection.aggregate(_pipeline).to_list(length=self.n_per_page)
|
|
93
|
+
|
|
94
|
+
# Count the total number of items
|
|
95
|
+
total = await _collection.count_documents(_query)
|
|
96
|
+
|
|
97
|
+
pages = total // self.n_per_page
|
|
98
|
+
if total % self.n_per_page > 0:
|
|
99
|
+
pages += 1
|
|
100
|
+
|
|
101
|
+
data = {
|
|
102
|
+
"items": [self.model.model_validate_doc(item) for item in items],
|
|
103
|
+
"total": total,
|
|
104
|
+
"size": len(items),
|
|
105
|
+
"page": page,
|
|
106
|
+
"pages": pages,
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return data
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Set of utilities to create and manage models."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import shortuuid
|
|
7
|
+
from pydantic import StringConstraints
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ShortUUID:
|
|
11
|
+
"""Custom type for short UUIDs with trigram prefixes"""
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def generate(cls, prefix: str) -> str:
|
|
15
|
+
"""Generate a valid short UUID with a given trigram prefix"""
|
|
16
|
+
if not re.match(r"^[a-z]{3}$", prefix):
|
|
17
|
+
raise ValueError("Prefix must be exactly 3 lowercase letters")
|
|
18
|
+
return f"{prefix}_{shortuuid.uuid()[:10]}"
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def validate(cls, value: str) -> str:
|
|
22
|
+
"""Validate the short UUID format"""
|
|
23
|
+
if not re.match(r"^[a-z]{3}_[a-zA-Z0-9]{10}$", value):
|
|
24
|
+
raise ValueError("Invalid short UUID format")
|
|
25
|
+
return value
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
ShortUUIDType = Annotated[str, StringConstraints(pattern=r"^[a-z]{3}_[a-zA-Z0-9]{10}$")]
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: fastapi-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Utilities for FastAPI projects.
|
|
5
|
+
Requires-Python: >=3.13
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: fastapi[standard]>=0.115.11
|
|
8
|
+
Requires-Dist: odmantic>=1.0.2
|
|
9
|
+
Requires-Dist: pydantic>=2.10.6
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.8.1
|
|
11
|
+
Requires-Dist: pytest>=8.3.5
|
|
12
|
+
Requires-Dist: pytest-asyncio>=0.25.3
|
|
13
|
+
Requires-Dist: shortuuid>=1.0.13
|
|
14
|
+
|
|
15
|
+
# FastAPI SDK
|
|
16
|
+
|
|
17
|
+
## Run tests
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv sync
|
|
21
|
+
pytest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Update requirements
|
|
25
|
+
|
|
26
|
+
You can add new requirements by using UV:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv add module_name
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Then update the requirements.txt:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv pip compile pyproject.toml -o requirements.txt
|
|
36
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
fastapi_sdk/controller.py,sha256=xGbzP-KZX1hsiY0uytsSLMa_IMDIQ-zBZAXYFNwUAn8,3462
|
|
2
|
+
fastapi_sdk/utils/model.py,sha256=rWpVHELs6wvh_7Q9wcEMJtaDT33IqW7gnbBzANaUtPk,871
|
|
3
|
+
fastapi_sdk/utils/schema.py,sha256=7nd5gV1izWOqmomnM_Q5PNIk-1QbUtZrIZ02id2RWNM,204
|
|
4
|
+
fastapi_sdk-0.1.0.dist-info/METADATA,sha256=FWFu1GTgqgbgBafFJTCSpba0haPwtPglaSZG1WImYXs,660
|
|
5
|
+
fastapi_sdk-0.1.0.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
6
|
+
fastapi_sdk-0.1.0.dist-info/top_level.txt,sha256=FcXWgEILikx1WO7Rm0-0bxb4Ck3nMSkzVHfy15Gag2o,12
|
|
7
|
+
fastapi_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastapi_sdk
|