Skip to content

Commit 0e01c85

Browse files
committed
updated readme and added example project
1 parent ac95a02 commit 0e01c85

File tree

21 files changed

+534
-0
lines changed

21 files changed

+534
-0
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
[![PyPI version](https://img.shields.io/pypi/v/ellar-sqlachemy.svg)](https://pypi.python.org/pypi/ellar-sqlachemy)
99
[![PyPI version](https://img.shields.io/pypi/pyversions/ellar-sqlachemy.svg)](https://pypi.python.org/pypi/ellar-sqlachemy)
1010

11+
## Project Status
12+
- 70% done
13+
- SQLAlchemy Table support with ModelSession
14+
- Migration custom revision directives
15+
- Documentation
16+
- File Field
17+
- Image Field
1118

1219
## Introduction
1320
Ellar SQLAlchemy Module simplifies the integration of SQLAlchemy and Alembic migration tooling into your ellar application.

examples/single-db/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Ellar SQLAlchemy Single Database Example
2+
Project Description
3+
4+
## Requirements
5+
Python >= 3.7
6+
Starlette
7+
Injector
8+
9+
## Project setup
10+
```shell
11+
pip install poetry
12+
```
13+
then,
14+
```shell
15+
poetry install
16+
```
17+
### Apply Migration
18+
```shell
19+
ellar db upgrade
20+
```
21+
22+
### Development Server
23+
```shell
24+
ellar runserver --reload
25+
```
26+
then, visit [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)

examples/single-db/db/__init__.py

Whitespace-only changes.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
Define endpoints routes in python class-based fashion
3+
example:
4+
5+
@Controller("/dogs", tag="Dogs", description="Dogs Resources")
6+
class MyController(ControllerBase):
7+
@get('/')
8+
def index(self):
9+
return {'detail': "Welcome Dog's Resources"}
10+
"""
11+
from ellar.common import Controller, ControllerBase, get, post, Body
12+
from pydantic import EmailStr
13+
from sqlalchemy import select
14+
15+
from .models.users import User
16+
17+
18+
@Controller
19+
class DbController(ControllerBase):
20+
21+
@get("/")
22+
def index(self):
23+
return {"detail": "Welcome Db Resource"}
24+
25+
@post("/users")
26+
def create_user(self, username: Body[str], email: Body[EmailStr]):
27+
session = User.get_db_session()
28+
user = User(username=username, email=email)
29+
30+
session.add(user)
31+
session.commit()
32+
33+
return user.dict()
34+
35+
@get("/users/{user_id:int}")
36+
def get_user_by_id(self, user_id: int):
37+
session = User.get_db_session()
38+
stmt = select(User).filter(User.id == user_id)
39+
user = session.execute(stmt).scalar()
40+
return user.dict()
41+
42+
@get("/users")
43+
async def get_all_users(self):
44+
session = User.get_db_session()
45+
stmt = select(User)
46+
rows = session.execute(stmt.offset(0).limit(100)).scalars()
47+
return [row.dict() for row in rows]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Multi-database configuration for Flask.
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,flask_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_flask_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: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import asyncio
2+
import typing as t
3+
from logging.config import fileConfig
4+
5+
from alembic import context
6+
from ellar.app import current_injector
7+
8+
from ellar_sqlalchemy.migrations import (
9+
MultipleDatabaseAlembicEnvMigration,
10+
SingleDatabaseAlembicEnvMigration,
11+
)
12+
from ellar_sqlalchemy.services import EllarSQLAlchemyService
13+
14+
db_service: EllarSQLAlchemyService = current_injector.get(EllarSQLAlchemyService)
15+
16+
# this is the Alembic Config object, which provides
17+
# access to the values within the .ini file in use.
18+
config = context.config
19+
20+
# Interpret the config file for Python logging.
21+
# This line sets up loggers basically.
22+
fileConfig(config.config_file_name) # type:ignore[arg-type]
23+
# logger = logging.getLogger("alembic.env")
24+
25+
26+
AlembicEnvMigrationKlass: t.Type[
27+
t.Union[MultipleDatabaseAlembicEnvMigration, SingleDatabaseAlembicEnvMigration]
28+
] = (
29+
MultipleDatabaseAlembicEnvMigration
30+
if len(db_service.engines) > 1
31+
else SingleDatabaseAlembicEnvMigration
32+
)
33+
34+
35+
# other values from the config, defined by the needs of env.py,
36+
# can be acquired:
37+
# my_important_option = config.get_main_option("my_important_option")
38+
# ... etc.
39+
40+
41+
alembic_env_migration = AlembicEnvMigrationKlass(db_service)
42+
43+
if context.is_offline_mode():
44+
alembic_env_migration.run_migrations_offline(context) # type:ignore[arg-type]
45+
else:
46+
asyncio.get_event_loop().run_until_complete(
47+
alembic_env_migration.run_migrations_online(context) # type:ignore[arg-type]
48+
)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<%!
2+
import re
3+
4+
%>"""${message}
5+
6+
Revision ID: ${up_revision}
7+
Revises: ${down_revision | comma,n}
8+
Create Date: ${create_date}
9+
10+
"""
11+
from alembic import op
12+
import sqlalchemy as sa
13+
${imports if imports else ""}
14+
15+
# revision identifiers, used by Alembic.
16+
revision = ${repr(up_revision)}
17+
down_revision = ${repr(down_revision)}
18+
branch_labels = ${repr(branch_labels)}
19+
depends_on = ${repr(depends_on)}
20+
21+
<%!
22+
from ellar.app import current_injector
23+
from ellar_sqlalchemy.services import EllarSQLAlchemyService
24+
25+
db_service = current_injector.get(EllarSQLAlchemyService)
26+
db_names = list(db_service.engines.keys())
27+
%>
28+
29+
% if len(db_names) > 1:
30+
31+
def upgrade(engine_name):
32+
globals()["upgrade_%s" % engine_name]()
33+
34+
35+
def downgrade(engine_name):
36+
globals()["downgrade_%s" % engine_name]()
37+
38+
39+
40+
## generate an "upgrade_<xyz>() / downgrade_<xyz>()" function
41+
## for each database name in the ini file.
42+
43+
% for db_name in db_names:
44+
45+
def upgrade_${db_name}():
46+
${context.get("%s_upgrades" % db_name, "pass")}
47+
48+
49+
def downgrade_${db_name}():
50+
${context.get("%s_downgrades" % db_name, "pass")}
51+
52+
% endfor
53+
54+
% else:
55+
56+
def upgrade():
57+
${upgrades if upgrades else "pass"}
58+
59+
60+
def downgrade():
61+
${downgrades if downgrades else "pass"}
62+
63+
% endif
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""first migration
2+
3+
Revision ID: b7712f83d45b
4+
Revises:
5+
Create Date: 2023-12-30 20:53:37.393009
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'b7712f83d45b'
14+
down_revision = None
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
20+
21+
def upgrade():
22+
# ### commands auto generated by Alembic - please adjust! ###
23+
op.create_table('user',
24+
sa.Column('id', sa.Integer(), nullable=False),
25+
sa.Column('username', sa.String(), nullable=False),
26+
sa.Column('email', sa.String(), nullable=False),
27+
sa.Column('created_date', sa.DateTime(), nullable=False),
28+
sa.Column('time_updated', sa.DateTime(), nullable=False),
29+
sa.PrimaryKeyConstraint('id', name=op.f('pk_user')),
30+
sa.UniqueConstraint('username', name=op.f('uq_user_username'))
31+
)
32+
# ### end Alembic commands ###
33+
34+
35+
def downgrade():
36+
# ### commands auto generated by Alembic - please adjust! ###
37+
op.drop_table('user')
38+
# ### end Alembic commands ###
39+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .users import User
2+
3+
__all__ = [
4+
'User',
5+
]

0 commit comments

Comments
 (0)