Skip to content

Commit 1bac830

Browse files
authored
docs: add fileobject example for litestar (#499)
1 parent 233dda6 commit 1bac830

File tree

2 files changed

+161
-2
lines changed

2 files changed

+161
-2
lines changed

examples/fastapi/fastapi_fileobject.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ class DocumentModel(base.UUIDBase):
5555

5656

5757
class DocumentService(service.SQLAlchemyAsyncRepositoryService[DocumentModel]):
58-
"""Author repository."""
58+
"""Document repository."""
5959

6060
class Repo(repository.SQLAlchemyAsyncRepository[DocumentModel]):
61-
"""Author repository."""
61+
"""Document repository."""
6262

6363
model_type = DocumentModel
6464

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
from typing import Annotated, Any, Optional, Union
2+
from uuid import UUID
3+
4+
import uvicorn
5+
from litestar import Controller, Litestar, delete, get, patch, post
6+
from litestar.datastructures import UploadFile
7+
from litestar.params import Dependency
8+
from pydantic import BaseModel, Field, computed_field
9+
from sqlalchemy.orm import Mapped, mapped_column
10+
11+
from advanced_alchemy.extensions.litestar import (
12+
AsyncSessionConfig,
13+
SQLAlchemyAsyncConfig,
14+
SQLAlchemyPlugin,
15+
base,
16+
filters,
17+
providers,
18+
repository,
19+
service,
20+
)
21+
from advanced_alchemy.types import FileObject, storages
22+
from advanced_alchemy.types.file_object.backends.obstore import ObstoreBackend
23+
from advanced_alchemy.types.file_object.data_type import StoredObject
24+
25+
# Object storage backend
26+
s3_backend = ObstoreBackend(
27+
key="local",
28+
fs="s3://static-files/",
29+
aws_endpoint="http://localhost:9000",
30+
aws_access_key_id="minioadmin",
31+
aws_secret_access_key="minioadmin", # noqa: S106
32+
)
33+
storages.register_backend(s3_backend)
34+
35+
36+
# SQLAlchemy Model
37+
class DocumentModel(base.UUIDBase):
38+
__tablename__ = "document"
39+
40+
name: Mapped[str]
41+
file: Mapped[FileObject] = mapped_column(StoredObject(backend="local"))
42+
43+
44+
# Pydantic Schema
45+
class Document(BaseModel):
46+
id: Optional[UUID]
47+
name: str
48+
file: Optional[FileObject] = Field(default=None, exclude=True)
49+
50+
@computed_field
51+
def file_url(self) -> Optional[Union[str, list[str]]]:
52+
if self.file is None:
53+
return None
54+
return self.file.sign()
55+
56+
57+
# Advanced Alchemy Service
58+
class DocumentService(service.SQLAlchemyAsyncRepositoryService[DocumentModel]):
59+
"""Document repository."""
60+
61+
class Repo(repository.SQLAlchemyAsyncRepository[DocumentModel]):
62+
"""Document repository."""
63+
64+
model_type = DocumentModel
65+
66+
repository_type = Repo
67+
68+
69+
# Litestar Controller
70+
class DocumentController(Controller):
71+
path = "/documents"
72+
dependencies = providers.create_service_dependencies(
73+
DocumentService,
74+
"documents_service",
75+
load=[DocumentModel.file],
76+
filters={"pagination_type": "limit_offset", "id_filter": UUID, "search": "name", "search_ignore_case": True},
77+
)
78+
79+
@get(path="/", response_model=service.OffsetPagination[Document])
80+
async def list_documents(
81+
self,
82+
documents_service: DocumentService,
83+
filters: Annotated[list[filters.FilterTypes], Dependency(skip_validation=True)],
84+
) -> service.OffsetPagination[Document]:
85+
results, total = await documents_service.list_and_count(*filters)
86+
return documents_service.to_schema(results, total, filters=filters, schema_type=Document)
87+
88+
@post(path="/")
89+
async def create_document(
90+
self,
91+
documents_service: DocumentService,
92+
name: str,
93+
file: Annotated[Optional[UploadFile], None] = None,
94+
) -> Document:
95+
obj = await documents_service.create(
96+
DocumentModel(
97+
name=name,
98+
file=FileObject(
99+
backend="local",
100+
filename=file.filename or "uploaded_file",
101+
content_type=file.content_type,
102+
content=await file.read(),
103+
)
104+
if file
105+
else None,
106+
)
107+
)
108+
return documents_service.to_schema(obj, schema_type=Document)
109+
110+
@get(path="/{document_id:uuid}")
111+
async def get_document(
112+
self,
113+
documents_service: DocumentService,
114+
document_id: UUID,
115+
) -> Document:
116+
obj = await documents_service.get(document_id)
117+
return documents_service.to_schema(obj, schema_type=Document)
118+
119+
@patch(path="/{document_id:uuid}")
120+
async def update_document(
121+
self,
122+
documents_service: DocumentService,
123+
document_id: UUID,
124+
name: Optional[str] = None,
125+
file: Annotated[Optional[UploadFile], None] = None,
126+
) -> Document:
127+
update_data: dict[str, Any] = {}
128+
if name is not None:
129+
update_data["name"] = name
130+
if file is not None:
131+
update_data["file"] = FileObject(
132+
backend="local",
133+
filename=file.filename or "uploaded_file",
134+
content_type=file.content_type,
135+
content=await file.read(),
136+
)
137+
138+
obj = await documents_service.update(update_data, item_id=document_id)
139+
return documents_service.to_schema(obj, schema_type=Document)
140+
141+
@delete(path="/{document_id:uuid}")
142+
async def delete_document(
143+
self,
144+
documents_service: DocumentService,
145+
document_id: UUID,
146+
) -> None:
147+
_ = await documents_service.delete(document_id)
148+
149+
150+
sqlalchemy_config = SQLAlchemyAsyncConfig(
151+
connection_string="sqlite+aiosqlite:///test.sqlite",
152+
session_config=AsyncSessionConfig(expire_on_commit=False),
153+
before_send_handler="autocommit",
154+
create_all=True,
155+
)
156+
app = Litestar(route_handlers=[DocumentController], plugins=[SQLAlchemyPlugin(config=sqlalchemy_config)])
157+
158+
if __name__ == "__main__":
159+
uvicorn.run(app, host="0.0.0.0", port=8000) # noqa: S104

0 commit comments

Comments
 (0)