Skip to content

Commit c839720

Browse files
committed
added sample for sqlalchemy
1 parent 018d51c commit c839720

File tree

26 files changed

+598
-2
lines changed

26 files changed

+598
-2
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
with:
1717
python-version: 3.8
1818
- name: Install Flit
19-
run: pip install flit ellar_jwt
19+
run: pip install flit ellar_jwt ellar_sql
2020
- name: Install Dependencies
2121
run: make install
2222
- name: Test

.github/workflows/test_full.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
with:
2020
python-version: ${{ matrix.python-version }}
2121
- name: Install Flit
22-
run: pip install flit ellar_jwt
22+
run: pip install flit ellar_jwt ellar_sql
2323
- name: Install Dependencies
2424
run: make install
2525
- name: Test
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## Introduction
2+
This is EllarSQL documentation `db_learning` project source code with all the illustrations
3+
4+
## Project setup
5+
```
6+
pip install -r requirements.txt
7+
```
8+
9+
## Important Quick Steps
10+
After environment setup, kindly follow instruction below
11+
12+
- apply migration `python manage.py db upgrade`
13+
- seed user data `python manage.py seed`
14+
15+
## Development Server
16+
```
17+
python manage.py runserver --reload
18+
```
19+
Visit Swagger Docs: [http://localhost:8000/docs](http://localhost:8000/docs)
20+
21+
22+
## Run Test
23+
```
24+
pytest
25+
```

samples/05-ellar-with-sqlalchemy/db_learning/__init__.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import ellar_cli.click as click
2+
from ellar.app import current_injector
3+
from ellar_sql import EllarSQLService
4+
5+
from db_learning.models import User
6+
7+
8+
@click.command("seed")
9+
@click.with_app_context
10+
def seed_user():
11+
db_service = current_injector.get(EllarSQLService)
12+
session = db_service.session_factory()
13+
14+
for i in range(300):
15+
session.add(User(username=f"username-{i+1}", email=f"user{i+1}[email protected]"))
16+
17+
session.commit()
18+
db_service.session_factory.remove()
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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=db_learning.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_QdZwHTfLkZQWQtAot-V6gbTHONMn3ekrl5jdcb5AOC8"
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
71+
72+
ELLAR_SQL: t.Dict[str, t.Any] = {
73+
"databases": {
74+
"default": "sqlite:///app.db",
75+
},
76+
"echo": True,
77+
"migration_options": {
78+
"directory": "migrations" # root directory will be determined based on where the module is instantiated.
79+
},
80+
"models": ["db_learning.models"],
81+
}
82+
83+
84+
class TestConfig(BaseConfig):
85+
DEBUG = False
86+
87+
ELLAR_SQL: t.Dict[str, t.Any] = {
88+
**DevelopmentConfig.ELLAR_SQL,
89+
"databases": {
90+
"default": "sqlite:///test.db",
91+
},
92+
"echo": False,
93+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import ellar.common as ecm
2+
from ellar.pydantic import EmailStr
3+
from ellar_sql import get_or_404, model
4+
5+
from .models import User
6+
7+
8+
@ecm.Controller
9+
class UsersController(ecm.ControllerBase):
10+
@ecm.post("/")
11+
def create_user(
12+
self,
13+
username: ecm.Body[str],
14+
email: ecm.Body[EmailStr],
15+
session: ecm.Inject[model.Session],
16+
):
17+
user = User(username=username, email=email)
18+
19+
session.add(user)
20+
session.commit()
21+
session.refresh(user)
22+
23+
return user.dict()
24+
25+
@ecm.get("/{user_id:int}")
26+
async def user_by_id(self, user_id: int):
27+
user: User = await get_or_404(User, user_id)
28+
return user.dict()
29+
30+
@ecm.get("/")
31+
async def user_list(self, session: ecm.Inject[model.Session]):
32+
stmt = model.select(User)
33+
rows = session.execute(stmt.offset(0).limit(100)).scalars()
34+
return [row.dict() for row in rows]
35+
36+
@ecm.get("/{user_id:int}")
37+
async def user_delete(self, user_id: int, session: ecm.Inject[model.Session]):
38+
user = get_or_404(User, user_id)
39+
session.delete(user)
40+
return {"detail": f"User id={user_id} Deleted successfully"}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Migration Setup for EllarSQL Models
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# template used to generate migration files
5+
file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
6+
7+
# set to 'true' to run the environment during
8+
# the 'revision' command, regardless of autogenerate
9+
# revision_environment = false
10+
11+
12+
# Logging configuration
13+
[loggers]
14+
keys = root,sqlalchemy,alembic,ellar_sqlalchemy_migrate
15+
16+
[handlers]
17+
keys = console
18+
19+
[formatters]
20+
keys = generic
21+
22+
[logger_root]
23+
level = WARN
24+
handlers = console
25+
qualname =
26+
27+
[logger_sqlalchemy]
28+
level = WARN
29+
handlers =
30+
qualname = sqlalchemy.engine
31+
32+
[logger_alembic]
33+
level = INFO
34+
handlers =
35+
qualname = alembic
36+
37+
[logger_ellar_sqlalchemy_migrate]
38+
level = INFO
39+
handlers =
40+
qualname = flask_migrate
41+
42+
[handler_console]
43+
class = StreamHandler
44+
args = (sys.stderr,)
45+
level = NOTSET
46+
formatter = generic
47+
48+
[formatter_generic]
49+
format = %(levelname)-5.5s [%(name)s] %(message)s
50+
datefmt = %H:%M:%S
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from logging.config import fileConfig
2+
3+
from alembic import context
4+
from ellar.app import current_injector
5+
from ellar.threading import run_as_async
6+
from ellar_sql.migrations import SingleDatabaseAlembicEnvMigration
7+
from ellar_sql.services import EllarSQLService
8+
9+
# this is the Alembic Config object, which provides
10+
# access to the values within the .ini file in use.
11+
config = context.config
12+
13+
# Interpret the config file for Python logging.
14+
# This line sets up loggers basically.
15+
fileConfig(config.config_file_name) # type:ignore[arg-type]
16+
17+
# logger = logging.getLogger("alembic.env")
18+
# other values from the config, defined by the needs of env.py,
19+
# can be acquired:
20+
# my_important_option = config.get_main_option("my_important_option")
21+
# ... etc.
22+
23+
24+
@run_as_async
25+
async def main() -> None:
26+
db_service: EllarSQLService = current_injector.get(EllarSQLService)
27+
28+
# initialize migration class
29+
alembic_env_migration = SingleDatabaseAlembicEnvMigration(db_service)
30+
31+
if context.is_offline_mode():
32+
alembic_env_migration.run_migrations_offline(context) # type:ignore[arg-type]
33+
else:
34+
await alembic_env_migration.run_migrations_online(context) # type:ignore[arg-type]
35+
36+
37+
main()

0 commit comments

Comments
 (0)