Skip to content

Commit 05b4138

Browse files
committed
Added image and field sample projects
1 parent 491c16f commit 05b4138

File tree

18 files changed

+421
-1
lines changed

18 files changed

+421
-1
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ dependencies = [
3535
"ellar-cli >= 0.3.7",
3636
"sqlalchemy >= 2.0.23",
3737
"alembic >= 1.10.0",
38-
"ellar-storage >= 0.1.2",
38+
"ellar-storage >= 0.1.4",
3939
"sqlalchemy-file >= 0.6.0",
4040
]
4141

samples/file-field-example/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Introduction
2+
This project illustrates how to use ImageField and FileField with EllarStorage package
3+
4+
## Requirements
5+
Python >= 3.8
6+
ellar-cli
7+
ellar-sql
8+
9+
## Project setup
10+
```
11+
pip install -r requirements.txt
12+
```
13+
14+
### Development Server
15+
```
16+
python manage.py runserver --reload
17+
```
18+
visit API [docs](http://localhost:8000/docs)

samples/file-field-example/file_field_example/__init__.py

Whitespace-only changes.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""
2+
Application Configurations
3+
Default Ellar Configurations are exposed here through `ConfigDefaultTypesMixin`
4+
Make changes and define your own configurations specific to your application
5+
6+
export ELLAR_CONFIG_MODULE=file_field_example.config:DevelopmentConfig
7+
"""
8+
9+
import typing as t
10+
11+
from ellar.common import IExceptionHandler, JSONResponse
12+
from ellar.core import ConfigDefaultTypesMixin
13+
from ellar.core.versioning import BaseAPIVersioning, DefaultAPIVersioning
14+
from ellar.pydantic import ENCODERS_BY_TYPE as encoders_by_type
15+
from starlette.middleware import Middleware
16+
17+
18+
class BaseConfig(ConfigDefaultTypesMixin):
19+
DEBUG: bool = False
20+
21+
DEFAULT_JSON_CLASS: t.Type[JSONResponse] = JSONResponse
22+
SECRET_KEY: str = "ellar_4BmOAHaR4N6K81UX22hwJQqVh93XJyiUYKrhgzWMKsc"
23+
24+
# injector auto_bind = True allows you to resolve types that are not registered on the container
25+
# For more info, read: https://injector.readthedocs.io/en/latest/index.html
26+
INJECTOR_AUTO_BIND = False
27+
28+
# jinja Environment options
29+
# https://jinja.palletsprojects.com/en/3.0.x/api/#high-level-api
30+
JINJA_TEMPLATES_OPTIONS: t.Dict[str, t.Any] = {}
31+
32+
# Application route versioning scheme
33+
VERSIONING_SCHEME: BaseAPIVersioning = DefaultAPIVersioning()
34+
35+
# Enable or Disable Application Router route searching by appending backslash
36+
REDIRECT_SLASHES: bool = False
37+
38+
# Define references to static folders in python packages.
39+
# eg STATIC_FOLDER_PACKAGES = [('boostrap4', 'statics')]
40+
STATIC_FOLDER_PACKAGES: t.Optional[t.List[t.Union[str, t.Tuple[str, str]]]] = []
41+
42+
# Define references to static folders defined within the project
43+
STATIC_DIRECTORIES: t.Optional[t.List[t.Union[str, t.Any]]] = []
44+
45+
# static route path
46+
STATIC_MOUNT_PATH: str = "/static"
47+
48+
CORS_ALLOW_ORIGINS: t.List[str] = ["*"]
49+
CORS_ALLOW_METHODS: t.List[str] = ["*"]
50+
CORS_ALLOW_HEADERS: t.List[str] = ["*"]
51+
ALLOWED_HOSTS: t.List[str] = ["*"]
52+
53+
# Application middlewares
54+
MIDDLEWARE: t.Sequence[Middleware] = []
55+
56+
# A dictionary mapping either integer status codes,
57+
# or exception class types onto callables which handle the exceptions.
58+
# Exception handler callables should be of the form
59+
# `handler(context:IExecutionContext, exc: Exception) -> response`
60+
# and may be either standard functions, or async functions.
61+
EXCEPTION_HANDLERS: t.List[IExceptionHandler] = []
62+
63+
# Object Serializer custom encoders
64+
SERIALIZER_CUSTOM_ENCODER: t.Dict[t.Any, t.Callable[[t.Any], t.Any]] = (
65+
encoders_by_type
66+
)
67+
68+
69+
class DevelopmentConfig(BaseConfig):
70+
DEBUG: bool = True
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .articles import ArticlesController
2+
from .attachments import AttachmentController
3+
from .books import BooksController
4+
5+
__all__ = [
6+
"BooksController",
7+
"ArticlesController",
8+
"AttachmentController",
9+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import typing as t
2+
3+
import ellar.common as ecm
4+
5+
from ellar_sql import model, paginate
6+
7+
from ..models import Article
8+
from .schema import ArticleSchema
9+
10+
11+
@ecm.Controller
12+
class ArticlesController(ecm.ControllerBase):
13+
@ecm.post("/", response={201: ArticleSchema})
14+
def create_article(
15+
self,
16+
title: ecm.Body[str],
17+
documents: ecm.File[t.List[ecm.UploadFile]],
18+
session: ecm.Inject[model.Session],
19+
):
20+
article = Article(title=title, documents=documents)
21+
session.add(article)
22+
23+
session.commit()
24+
session.refresh(article)
25+
26+
return article
27+
28+
@ecm.get("/")
29+
@paginate(model=Article, item_schema=ArticleSchema)
30+
def list_articles(self):
31+
return {}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import ellar.common as ecm
2+
3+
from ellar_sql import model, paginate
4+
5+
from ..models import Attachment
6+
from .schema import AttachmentSchema
7+
8+
9+
@ecm.Controller
10+
class AttachmentController(ecm.ControllerBase):
11+
@ecm.post("/", response={201: AttachmentSchema})
12+
def create_attachment(
13+
self,
14+
name: ecm.Body[str],
15+
content: ecm.File[ecm.UploadFile],
16+
session: ecm.Inject[model.Session],
17+
):
18+
attachment = Attachment(name=name, content=content)
19+
session.add(attachment)
20+
21+
session.commit()
22+
session.refresh(attachment)
23+
24+
return attachment
25+
26+
@ecm.get("/")
27+
@paginate(model=Attachment, item_schema=AttachmentSchema)
28+
def list_attachments(self):
29+
return {}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import ellar.common as ecm
2+
3+
from ellar_sql import model, paginate
4+
5+
from ..models import Book
6+
from .schema import BookSchema
7+
8+
9+
@ecm.Controller
10+
class BooksController(ecm.ControllerBase):
11+
@ecm.post("/", response={201: BookSchema})
12+
def create_book(
13+
self,
14+
title: ecm.Body[str],
15+
cover: ecm.File[ecm.UploadFile],
16+
session: ecm.Inject[model.Session],
17+
):
18+
book = Book(title=title, cover=cover)
19+
session.add(book)
20+
21+
session.commit()
22+
session.refresh(book)
23+
24+
return book
25+
26+
@ecm.get("/")
27+
@paginate(model=Book, item_schema=BookSchema)
28+
def list_book(self):
29+
return {}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import typing as t
2+
3+
import ellar.common as ecm
4+
from ellar.app import current_injector
5+
from ellar.core import Request
6+
from ellar.pydantic import model_validator
7+
from pydantic import HttpUrl
8+
9+
from ellar_sql.model.mixins import ModelDataExportMixin
10+
from ellar_sql.model.typeDecorator.file import File
11+
12+
13+
class BookSchema(ecm.Serializer):
14+
id: int
15+
title: str
16+
cover: HttpUrl
17+
18+
thumbnail: HttpUrl
19+
20+
@model_validator(mode="before")
21+
def cover_validate_before(cls, values) -> t.Any:
22+
req: Request = (
23+
current_injector.get(ecm.IExecutionContext)
24+
.switch_to_http_connection()
25+
.get_request()
26+
)
27+
values = values.dict() if isinstance(values, ModelDataExportMixin) else values
28+
29+
cover: File = values["cover"]
30+
31+
values["cover"] = str(
32+
req.url_for("storage:download", path=cover["path"])
33+
) # from ellar_storage.StorageController
34+
values["thumbnail"] = str(
35+
req.url_for("storage:download", path=cover["files"][1])
36+
) # from ellar_storage.StorageController
37+
38+
return values
39+
40+
41+
class ArticleSchema(ecm.Serializer):
42+
id: int
43+
title: str
44+
documents: t.List[HttpUrl]
45+
46+
@model_validator(mode="before")
47+
def model_validate_before(cls, values) -> t.Any:
48+
req: Request = (
49+
current_injector.get(ecm.IExecutionContext)
50+
.switch_to_http_connection()
51+
.get_request()
52+
)
53+
values = values.dict() if isinstance(values, ModelDataExportMixin) else values
54+
55+
values["documents"] = [
56+
str(req.url_for("storage:download", path=item["path"]))
57+
for item in values["documents"]
58+
]
59+
60+
return values
61+
62+
63+
class AttachmentSchema(ecm.Serializer):
64+
id: int
65+
name: str
66+
content: HttpUrl
67+
68+
@model_validator(mode="before")
69+
def model_validate_before(cls, values) -> t.Any:
70+
req: Request = (
71+
current_injector.get(ecm.IExecutionContext)
72+
.switch_to_http_connection()
73+
.get_request()
74+
)
75+
values = values.dict() if isinstance(values, ModelDataExportMixin) else values
76+
77+
values["content"] = str(
78+
req.url_for("storage:download", path=values["content"]["path"])
79+
)
80+
return values
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from .article import Article
2+
from .attachment import Attachment
3+
from .book import Book
4+
5+
__all__ = [
6+
"Book",
7+
"Article",
8+
"Attachment",
9+
]

0 commit comments

Comments
 (0)