Skip to content

Commit 864fee6

Browse files
committed
initial commit
0 parents  commit 864fee6

13 files changed

Lines changed: 1638 additions & 0 deletions

File tree

.vscode/settings.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"[python]": {
3+
"editor.defaultFormatter": "ms-python.black-formatter"
4+
},
5+
"python.formatting.provider": "none"
6+
}

README.md

Whitespace-only changes.

object_api/__init__.py

Whitespace-only changes.

object_api/app.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from __future__ import annotations
2+
from contextlib import contextmanager
3+
from typing import Generator
4+
from fastapi import FastAPI
5+
from pydantic import Field
6+
from sqlalchemy.future.engine import Engine
7+
8+
from scheduler import Scheduler
9+
from sqlmodel import SQLModel, Session, create_engine
10+
11+
from object_api.entity import Entity
12+
from object_api.utils.has_post_init import HasPostInitMixin
13+
14+
15+
class App(FastAPI, HasPostInitMixin):
16+
CURRENT_APP: App = Field(None, init=False)
17+
18+
scheduler: Scheduler = Field(default_factory=Scheduler, init=False)
19+
entity_classes: list[type[Entity]] = Field([], init=False)
20+
db_engine: Engine = Field(None, init=False)
21+
debug: bool = True
22+
23+
def __post_init__(self):
24+
if not self.db_engine:
25+
sqlite_file_name = "database.db"
26+
sqlite_url = f"sqlite:///{sqlite_file_name}"
27+
connect_args = {"check_same_thread": False}
28+
self.db_engine = create_engine(
29+
sqlite_url, echo=self.debug, connect_args=connect_args
30+
)
31+
self.CURRENT_APP = self
32+
33+
self.build()
34+
35+
return super().__post_init__()
36+
37+
def build(self):
38+
self.create_db_and_tables()
39+
self.build_services()
40+
self.build_routers()
41+
42+
def create_db_and_tables(self):
43+
SQLModel.metadata.create_all(self.db_engine)
44+
45+
def build_services(self):
46+
for entity_class in self.entity_classes:
47+
entity_class.Meta.service.build_services(entity_class)
48+
49+
def build_routers(self):
50+
for entity_class in self.entity_classes:
51+
entity_class.Meta.router.build_router(entity_class)
52+
53+
_object_api_app_active_session: Session = Field(None, init=False)
54+
55+
@contextmanager
56+
async def session(self) -> Generator[None, None, None]:
57+
if self._object_api_app_active_session:
58+
if not self._object_api_app_active_session.is_active:
59+
raise RuntimeError(
60+
"Session is already closed. Please use a new session for each request."
61+
)
62+
63+
yield self._object_api_app_active_session
64+
return
65+
66+
with Session(self.db_engine) as session:
67+
self._object_api_app_active_session = session
68+
yield session
69+
self._object_api_app_active_session = None
70+
71+
@contextmanager
72+
async def as_current(self) -> Generator[App, None, None]:
73+
old_app = self.CURRENT_APP
74+
self.CURRENT_APP = self
75+
yield
76+
self.CURRENT_APP = old_app
77+
78+
def start(self):
79+
self.start_services()
80+
81+
def stop(self):
82+
self.stop_services()
83+
84+
def start_services(self):
85+
for entity_class in self.entity_classes:
86+
if entity_class.Meta.service:
87+
entity_class.Meta.service.start_service(entity_class)
88+
89+
def stop_services(self):
90+
for entity_class in self.entity_classes:
91+
if entity_class.Meta.service:
92+
entity_class.Meta.service.stop_service(entity_class)

object_api/entity.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from __future__ import annotations
2+
3+
from abc import ABC, ABCMeta, abstractmethod
4+
from typing import Annotated, Any, Generic, Self, TypeVar
5+
from uuid import UUID
6+
import uuid
7+
import fastapi
8+
import httpx
9+
10+
from pydantic import UUID4, BaseModel, Field
11+
from fastapi import APIRouter
12+
from sqlmodel import SQLModel
13+
from object_api.app import App
14+
15+
from object_api.router_builder import RouterBuilder
16+
from object_api.service_builder import ServiceBuilder
17+
from object_api.utils.has_post_init import HasPostInitMixin
18+
from object_api.utils.python import attr_is_list
19+
20+
21+
T = TypeVar("T", bound=BaseModel)
22+
23+
24+
class Entity(Generic[T], HasPostInitMixin, SQLModel, ABC, table=False):
25+
class Meta:
26+
router = RouterBuilder()
27+
service = ServiceBuilder()
28+
29+
id: UUID4 = Field(default_factory=uuid.uuid4)
30+
31+
class CreateArgs(BaseModel):
32+
pass
33+
34+
class UpdateArgs(BaseModel):
35+
pass
36+
37+
def __post_init__(self):
38+
self._ALL_ENTITIES[self.id] = self
39+
40+
def __del__(self):
41+
del self._ALL_ENTITIES[self.id]
42+
43+
@Meta.router.post("")
44+
@classmethod
45+
def create(cls, args: CreateArgs) -> Self:
46+
return cls(**args.dict())
47+
48+
@Meta.router.get(f"{{id}}")
49+
@classmethod
50+
async def get_by_id(cls, id: UUID4) -> T:
51+
return cls._ALL_ENTITIES[id]
52+
53+
@Meta.router.get("")
54+
@classmethod
55+
async def get_all(cls, page: int = 0, page_size: int = 100) -> list[T]:
56+
return list(cls._ALL_ENTITIES.values())[
57+
page * page_size : (page + 1) * page_size
58+
]
59+
60+
@Meta.router.patch()
61+
def update(self, updates: UpdateArgs) -> None:
62+
for key, value in updates.dict().items():
63+
setattr(self, key, value)
64+
65+
@Meta.router.post("delete")
66+
@Meta.router.delete("")
67+
def delete(self) -> None:
68+
del self._ALL_ENTITIES[self.id]
69+
del self
70+
71+
@property
72+
def router(self) -> APIRouter:
73+
if not self.Meta.router.router:
74+
self.Meta.router.build_router(self)
75+
return self.Meta.router.router
76+
77+
@property
78+
def servicemethods(self) -> list[callable]:
79+
return self.Meta.service.servicemethods(self)

0 commit comments

Comments
 (0)