Skip to content

Commit 2ac5264

Browse files
authored
Merge pull request #10 from python-ellar/docs_jan_18
Documentation and Code refactor
2 parents 1d90469 + ff04943 commit 2ac5264

File tree

109 files changed

+4112
-1425
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+4112
-1425
lines changed

Makefile

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ clean: ## Removing cached python compiled files
99
find . -name \*pyo | xargs rm -fv
1010
find . -name \*~ | xargs rm -fv
1111
find . -name __pycache__ | xargs rm -rfv
12+
find . -name .pytest_cache | xargs rm -rfv
1213
find . -name .ruff_cache | xargs rm -rfv
1314

1415
install: ## Install dependencies
@@ -23,14 +24,14 @@ lint:fmt ## Run code linters
2324
mypy ellar_sql
2425

2526
fmt format:clean ## Run code formatters
26-
ruff format ellar_sql tests
27-
ruff check --fix ellar_sql tests
27+
ruff format ellar_sql tests examples
28+
ruff check --fix ellar_sql tests examples
2829

29-
test: ## Run tests
30-
pytest tests
30+
test:clean ## Run tests
31+
pytest
3132

32-
test-cov: ## Run tests with coverage
33-
pytest --cov=ellar_sql --cov-report term-missing tests
33+
test-cov:clean ## Run tests with coverage
34+
pytest --cov=ellar_sql --cov-report term-missing
3435

3536
pre-commit-lint: ## Runs Requires commands during pre-commit
3637
make clean

README.md

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,30 @@
22
<a href="#" target="blank"><img src="https://python-ellar.github.io/ellar/img/EllarLogoB.png" width="200" alt="Ellar Logo" /></a>
33
</p>
44

5-
![Test](https://github.com/python-ellar/ellar-sqlachemy/actions/workflows/test_full.yml/badge.svg)
6-
![Coverage](https://img.shields.io/codecov/c/github/python-ellar/ellar-sqlalchemy)
7-
[![PyPI version](https://badge.fury.io/py/ellar-sqlachemy.svg)](https://badge.fury.io/py/ellar-sqlachemy)
8-
[![PyPI version](https://img.shields.io/pypi/v/ellar-sqlachemy.svg)](https://pypi.python.org/pypi/ellar-sqlachemy)
9-
[![PyPI version](https://img.shields.io/pypi/pyversions/ellar-sqlachemy.svg)](https://pypi.python.org/pypi/ellar-sqlachemy)
10-
11-
## Project Status
12-
13-
- [ ] Overall Completion - 85% done
14-
- [ ] Tests - 90% Complete
15-
- [x] Model class that transforms to SQLAlchemy Models or DeclarativeBase based on configuration
16-
- [x] Pydantic-like way to exporting model to dictionary object eg:`model.dict(exclude={'x', 'y', 'z'})`
17-
- [x] Support multiple database useful in models and queries
18-
- [x] Session management during request scope and outside request
19-
- [x] Service to manage SQLAlchemy Engine and Session creation, and Migration for async and sync Engines and Sessions
20-
- [x] Async first approach to database migration using Alembic
21-
- [x] Expose all Alembic commands to Ellar CLI
22-
- [x] Module to config and setup SQLAlchemy dependencies and migration path
23-
- [x] SQLAlchemy Pagination for both Templating and API routes
24-
- [x] File and Image SQLAlchemy Columns integration with ellar storage
25-
- [ ] SQLAlchemy Django Like Query
26-
- [ ] Documentation
5+
![Test](https://github.com/python-ellar/ellar-sql/actions/workflows/test_full.yml/badge.svg)
6+
![Coverage](https://img.shields.io/codecov/c/github/python-ellar/ellar-sql)
7+
[![PyPI version](https://badge.fury.io/py/ellar-sql.svg)](https://badge.fury.io/py/ellar-sql)
8+
[![PyPI version](https://img.shields.io/pypi/v/ellar-sql.svg)](https://pypi.python.org/pypi/ellar-sql)
9+
[![PyPI version](https://img.shields.io/pypi/pyversions/ellar-sql.svg)](https://pypi.python.org/pypi/ellar-sql)
10+
2711

2812
## Introduction
29-
Ellar SQLAlchemy Module simplifies the integration of SQLAlchemy and Alembic migration tooling into your ellar application.
13+
EllarSQL Module adds support for `SQLAlchemy` and `Alembic` package to your Ellar application
3014

3115
## Installation
3216
```shell
33-
$(venv) pip install ellar-sqlalchemy
17+
$(venv) pip install ellar-sql
3418
```
3519

20+
This library was inspired by [Flask-SQLAlchemy](https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/){target="_blank"}
21+
3622
## Features
37-
- Automatic table name
38-
- Session management during request and after request
39-
- Support both async/sync SQLAlchemy operations in Session, Engine, and Connection.
40-
- Multiple Database Support
41-
- Database migrations for both single and multiple databases either async/sync database engine
23+
24+
- Migration
25+
- Single/Multiple Database
26+
- Pagination
27+
- Compatible with SQLAlchemy tools
28+
4229

4330
## **Usage**
4431
In your ellar application, create a module called `db` or any name of your choice,
@@ -56,7 +43,7 @@ from ellar_sql.model import Model
5643

5744

5845
class Base(Model):
59-
__base_config__ = {'make_declarative_base': True}
46+
__base_config__ = {'as_base': True}
6047
__database__ = 'default'
6148

6249
created_date: Mapped[datetime] = mapped_column(
@@ -104,7 +91,7 @@ from .controllers import DbController
10491
},
10592
echo=True,
10693
migration_options={
107-
'directory': '__main__/migrations'
94+
'directory': 'my_migrations_folder'
10895
},
10996
models=['db.models.users']
11097
)

docs/advance/index.md

Whitespace-only changes.

docs/index.md

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<style>
2+
.md-content .md-typeset h1 { display: none; }
3+
</style>
4+
<p align="center">
5+
<a href="#" target="blank"><img src="https://python-ellar.github.io/ellar/img/EllarLogoB.png" width="200" alt="Ellar Logo" /></a>
6+
</p>
7+
<p align="center">EllarSQL is an SQL database Ellar Module.</p>
8+
9+
![Test](https://github.com/python-ellar/ellar-sql/actions/workflows/test_full.yml/badge.svg)
10+
![Coverage](https://img.shields.io/codecov/c/github/python-ellar/ellar-sql)
11+
[![PyPI version](https://badge.fury.io/py/ellar-sql.svg)](https://badge.fury.io/py/ellar-sql)
12+
[![PyPI version](https://img.shields.io/pypi/v/ellar-sql.svg)](https://pypi.python.org/pypi/ellar-sql)
13+
[![PyPI version](https://img.shields.io/pypi/pyversions/ellar-sql.svg)](https://pypi.python.org/pypi/ellar-sql)
14+
15+
EllarSQL is an SQL database module, leveraging the robust capabilities of [SQLAlchemy](https://www.sqlalchemy.org/) to
16+
seamlessly interact with SQL databases through Python code and objects.
17+
18+
Engineered with precision, EllarSQL is meticulously designed to streamline the integration of **SQLAlchemy** within your
19+
Ellar application. It introduces discerning usage patterns around pivotal objects
20+
such as **model**, **session**, and **engine**, ensuring an efficient and coherent workflow.
21+
22+
Notably, EllarSQL refrains from altering the fundamental workings or usage of SQLAlchemy.
23+
This documentation is focused on the meticulous setup of EllarSQL. For an in-depth exploration of SQLAlchemy,
24+
we recommend referring to the comprehensive [SQLAlchemy documentation](https://docs.sqlalchemy.org/).
25+
26+
## **Feature Highlights**
27+
EllarSQL comes packed with a set of awesome features designed:
28+
29+
- **Migration**: Enjoy an async-first migration solution that seamlessly handles both single and multiple database setups and for both async and sync database engines configuration.
30+
31+
- **Single/Multiple Database**: EllarSQL provides an intuitive setup for models with different databases, allowing you to manage your data across various sources effortlessly.
32+
33+
- **Pagination**: EllarSQL introduces SQLAlchemy Paginator for API/Templated routes, along with support for other fantastic SQLAlchemy pagination tools.
34+
35+
- **Unlimited Compatibility**: EllarSQL plays nice with the entire SQLAlchemy ecosystem. Whether you're using third-party tools or exploring the vast SQLAlchemy landscape, EllarSQL seamlessly integrates with your preferred tooling.
36+
37+
## **Requirements**
38+
EllarSQL core dependencies includes:
39+
40+
- Python >= 3.8
41+
- Ellar >= 0.6.7
42+
- SQLAlchemy >= 2.0.16
43+
- Alembic >= 1.10.0
44+
45+
## **Installation**
46+
47+
```shell
48+
pip install ellar-sql
49+
```
50+
51+
## **Quick Example**
52+
Let's create a simple `User` model.
53+
```python
54+
from ellar_sql import model
55+
56+
57+
class User(model.Model):
58+
id: model.Mapped[int] = model.mapped_column(model.Integer, primary_key=True)
59+
username: model.Mapped[str] = model.mapped_column(model.String, unique=True, nullable=False)
60+
email: model.Mapped[str] = model.mapped_column(model.String)
61+
```
62+
Let's create `app.db` with `User` table in it. For that we need to set up `EllarSQLService` as shown below:
63+
64+
```python
65+
from ellar_sql import EllarSQLService
66+
67+
db_service = EllarSQLService(
68+
databases='sqlite:///app.db',
69+
echo=True,
70+
)
71+
72+
db_service.create_all()
73+
```
74+
If you check your execution directory, you will see `sqlite` directory with `app.db`.
75+
76+
Let's populate our `User` table. To do, we need a session, which is available at `db_service.session_factory`
77+
78+
```python
79+
from ellar_sql import EllarSQLService, model
80+
81+
82+
class User(model.Model):
83+
id: model.Mapped[int] = model.mapped_column(model.Integer, primary_key=True)
84+
username: model.Mapped[str] = model.mapped_column(model.String, unique=True, nullable=False)
85+
email: model.Mapped[str] = model.mapped_column(model.String)
86+
87+
88+
db_service = EllarSQLService(
89+
databases='sqlite:///app.db',
90+
echo=True,
91+
)
92+
93+
db_service.create_all()
94+
95+
session = db_service.session_factory()
96+
97+
for i in range(50):
98+
session.add(User(username=f'username-{i+1}', email=f'user{i+1}[email protected]'))
99+
100+
101+
session.commit()
102+
rows = session.execute(model.select(User)).scalars()
103+
104+
all_users = [row.dict() for row in rows]
105+
assert len(all_users) == 50
106+
107+
session.close()
108+
```
109+
110+
We have successfully seed `50` users to `User` table in `app.db`.
111+
112+
I know at this point you want to know more, so let's dive deep into the documents and [get started](https://githut.com/python-ellar/ellar-sql/models/).

docs/migrations/env.md

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# **Alembic Env**
2+
3+
In the generated migration template, EllarSQL adopts an async-first approach for handling migration file generation.
4+
This approach simplifies the execution of migrations for both `Session`, `Engine`, `AsyncSession`, and `AsyncEngine`,
5+
but it also introduces a certain level of complexity.
6+
7+
```python
8+
from logging.config import fileConfig
9+
10+
from alembic import context
11+
from ellar.app import current_injector
12+
from ellar.threading import execute_coroutine_with_sync_worker
13+
14+
from ellar_sql.migrations import SingleDatabaseAlembicEnvMigration
15+
from ellar_sql.services import EllarSQLService
16+
17+
# this is the Alembic Config object, which provides
18+
# access to the values within the .ini file in use.
19+
config = context.config
20+
21+
# Interpret the config file for Python logging.
22+
# This line sets up loggers basically.
23+
fileConfig(config.config_file_name) # type:ignore[arg-type]
24+
25+
# logger = logging.getLogger("alembic.env")
26+
# other values from the config, defined by the needs of env.py,
27+
# can be acquired:
28+
# my_important_option = config.get_main_option("my_important_option")
29+
# ... etc.
30+
31+
32+
async def main() -> None:
33+
db_service: EllarSQLService = current_injector.get(EllarSQLService)
34+
35+
# initialize migration class
36+
alembic_env_migration = SingleDatabaseAlembicEnvMigration(db_service)
37+
38+
if context.is_offline_mode():
39+
alembic_env_migration.run_migrations_offline(context) # type:ignore[arg-type]
40+
else:
41+
await alembic_env_migration.run_migrations_online(context) # type:ignore[arg-type]
42+
43+
44+
execute_coroutine_with_sync_worker(main())
45+
```
46+
47+
The EllarSQL migration package provides two main migration classes:
48+
49+
- **SingleDatabaseAlembicEnvMigration**: Manages migrations for a **single** database configuration, catering to both `Engine` and `AsyncEngine`.
50+
- **MultipleDatabaseAlembicEnvMigration**: Manages migrations for **multiple** database configurations, covering both `Engine` and `AsyncEngine`.
51+
52+
## **Customizing the Env file**
53+
54+
To customize or edit the Env file, it is recommended to inherit from either `SingleDatabaseAlembicEnvMigration` or `MultipleDatabaseAlembicEnvMigration` based on your specific configuration. Make the necessary changes within the inherited class.
55+
56+
If you prefer to write something from scratch, then the abstract class `AlembicEnvMigrationBase` is the starting point. This class includes three abstract methods and expects a `EllarSQLService` during initialization, as demonstrated below:
57+
58+
```python
59+
class AlembicEnvMigrationBase:
60+
def __init__(self, db_service: EllarSQLService) -> None:
61+
self.db_service = db_service
62+
self.use_two_phase = db_service.migration_options.use_two_phase
63+
64+
@abstractmethod
65+
def default_process_revision_directives(
66+
self,
67+
context: "MigrationContext",
68+
revision: RevisionArgs,
69+
directives: t.List["MigrationScript"],
70+
) -> t.Any:
71+
pass
72+
73+
@abstractmethod
74+
def run_migrations_offline(self, context: "EnvironmentContext") -> None:
75+
pass
76+
77+
@abstractmethod
78+
async def run_migrations_online(self, context: "EnvironmentContext") -> None:
79+
pass
80+
```
81+
82+
The `run_migrations_online` and `run_migrations_offline` are all similar to the same function from Alembic env.py template.
83+
The `default_process_revision_directives` is a callback is used to prevent an auto-migration from being generated
84+
when there are no changes to the schema described in details [here](http://alembic.zzzcomputing.com/en/latest/cookbook.html)
85+
86+
### Example
87+
```python
88+
import logging
89+
from logging.config import fileConfig
90+
91+
from alembic import context
92+
from ellar_sql.migrations import AlembicEnvMigrationBase
93+
from ellar_sql.model.database_binds import get_metadata
94+
from ellar.app import current_injector
95+
from ellar.threading import execute_coroutine_with_sync_worker
96+
from ellar_sql.services import EllarSQLService
97+
98+
# This is the Alembic Config object, which provides
99+
# access to the values within the .ini file in use.
100+
config = context.config
101+
logger = logging.getLogger("alembic.env")
102+
# Interpret the config file for Python logging.
103+
# This line sets up loggers essentially.
104+
fileConfig(config.config_file_name) # type:ignore[arg-type]
105+
106+
class MyCustomMigrationEnv(AlembicEnvMigrationBase):
107+
def default_process_revision_directives(
108+
self,
109+
context,
110+
revision,
111+
directives,
112+
) -> None:
113+
if getattr(context.config.cmd_opts, "autogenerate", False):
114+
script = directives[0]
115+
if script.upgrade_ops.is_empty():
116+
directives[:] = []
117+
logger.info("No changes in schema detected.")
118+
119+
def run_migrations_offline(self, context: "EnvironmentContext") -> None:
120+
"""Run migrations in 'offline' mode.
121+
122+
This configures the context with just a URL
123+
and not an Engine, though an Engine is acceptable
124+
here as well. By skipping the Engine creation
125+
we don't even need a DBAPI to be available.
126+
127+
Calls to context.execute() here emit the given string to the
128+
script output.
129+
130+
"""
131+
pass
132+
133+
async def run_migrations_online(self, context: "EnvironmentContext") -> None:
134+
"""Run migrations in 'online' mode.
135+
136+
In this scenario, we need to create an Engine
137+
and associate a connection with the context.
138+
139+
"""
140+
key, engine = self.db_service.engines.popitem()
141+
metadata = get_metadata(key, certain=True).metadata
142+
143+
conf_args = {}
144+
conf_args.setdefault(
145+
"process_revision_directives", self.default_process_revision_directives
146+
)
147+
148+
with engine.connect() as connection:
149+
context.configure(
150+
connection=connection,
151+
target_metadata=metadata,
152+
**conf_args
153+
)
154+
155+
with context.begin_transaction():
156+
context.run_migrations()
157+
158+
async def main() -> None:
159+
db_service: EllarSQLService = current_injector.get(EllarSQLService)
160+
161+
# initialize migration class
162+
alembic_env_migration = MyCustomMigrationEnv(db_service)
163+
164+
if context.is_offline_mode():
165+
alembic_env_migration.run_migrations_offline(context)
166+
else:
167+
await alembic_env_migration.run_migrations_online(context)
168+
169+
execute_coroutine_with_sync_worker(main())
170+
```
171+
172+
This migration environment class, `MyCustomMigrationEnv`, inherits from `AlembicEnvMigrationBase`
173+
and provides the necessary methods for offline and online migrations.
174+
It utilizes the `EllarSQLService` to obtain the database engines and metadata for the migration process.
175+
The `main` function initializes and executes the migration class, with specific handling for offline and online modes.

0 commit comments

Comments
 (0)