Skip to content

Commit 7017b96

Browse files
committed
Added Documentation, code refactor and drop file system for
1 parent 1d90469 commit 7017b96

Some content is hidden

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

63 files changed

+2305
-1224
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
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)
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)
1010

1111
## Project Status
1212

@@ -56,7 +56,7 @@ from ellar_sql.model import Model
5656

5757

5858
class Base(Model):
59-
__base_config__ = {'make_declarative_base': True}
59+
__base_config__ = {'as_base': True}
6060
__database__ = 'default'
6161

6262
created_date: Mapped[datetime] = mapped_column(

docs/advance/index.md

Whitespace-only changes.

docs/index.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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 Mastery**: 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+
- **Database Diversity**: Dive into the world of multiple databases with ease. EllarSQL provides an intuitive setup for models with different databases, allowing you to manage your data across various sources effortlessly.
32+
33+
- **Pagination Perfection**: EllarSQL introduces SQLAlchemy Paginator for API/Templated routes, along with support for other fantastic SQLAlchemy pagination tools.
34+
35+
- **Extra Column Field**: EllarSQL spices things up with additional column types like **GUID**, **IPAddress**, **File**, and **Image** Fields.
36+
37+
- **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.
38+
39+
## **Requirements**
40+
EllarSQL core dependencies includes:
41+
42+
- Python version >= 3.8
43+
- Ellar Framework >= 0.6.7
44+
- SQLAlchemy ORM >= 2.0.16
45+
- Alembic >= 1.10.0
46+
- Pillow >= 10.1.0
47+
- Python-Magic >= 0.4.27
48+
49+
## **Installation**
50+
51+
```shell
52+
pip install ellar-sql
53+
```
54+
55+
## **Quick Example**
56+
Let's create a simple `User` model.
57+
```python
58+
from ellar_sql import model
59+
60+
61+
class User(model.Model):
62+
id: model.Mapped[int] = model.mapped_column(model.Integer, primary_key=True)
63+
username: model.Mapped[str] = model.mapped_column(model.String, unique=True, nullable=False)
64+
email: model.Mapped[str] = model.mapped_column(model.String)
65+
```
66+
Let's create `app.db` with `User` table in it. For that we need to set up `EllarSQLService` as shown below:
67+
68+
```python
69+
from ellar_sql import EllarSQLService
70+
71+
db_service = EllarSQLService(
72+
databases='sqlite:///app.db',
73+
echo=True,
74+
)
75+
76+
db_service.create_all()
77+
```
78+
If you check your execution directory, you will see `sqlite` directory with `app.db`.
79+
80+
Let's populate our `User` table. To do, we need a session, which is available at `db_service.session_factory`
81+
82+
```python
83+
from ellar_sql import EllarSQLService, model
84+
85+
86+
class User(model.Model):
87+
id: model.Mapped[int] = model.mapped_column(model.Integer, primary_key=True)
88+
username: model.Mapped[str] = model.mapped_column(model.String, unique=True, nullable=False)
89+
email: model.Mapped[str] = model.mapped_column(model.String)
90+
91+
92+
db_service = EllarSQLService(
93+
databases='sqlite:///app.db',
94+
echo=True,
95+
)
96+
97+
db_service.create_all()
98+
99+
session = db_service.session_factory()
100+
101+
for i in range(50):
102+
session.add(User(username=f'username-{i+1}', email=f'user{i+1}[email protected]'))
103+
104+
105+
session.commit()
106+
rows = session.execute(model.select(User)).scalars()
107+
108+
all_users = [row.dict() for row in rows]
109+
assert len(all_users) == 50
110+
111+
session.close()
112+
```
113+
114+
We have successfully seed `50` users to `User` table in `app.db`.
115+
116+
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: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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+
```python
7+
from logging.config import fileConfig
8+
9+
from alembic import context
10+
from ellar.app import current_injector
11+
from ellar.threading import execute_coroutine_with_sync_worker
12+
13+
from ellar_sql.migrations import SingleDatabaseAlembicEnvMigration
14+
from ellar_sql.services import EllarSQLService
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+
24+
# logger = logging.getLogger("alembic.env")
25+
# other values from the config, defined by the needs of env.py,
26+
# can be acquired:
27+
# my_important_option = config.get_main_option("my_important_option")
28+
# ... etc.
29+
30+
31+
async def main() -> None:
32+
db_service: EllarSQLService = current_injector.get(EllarSQLService)
33+
34+
# initialize migration class
35+
alembic_env_migration = SingleDatabaseAlembicEnvMigration(db_service)
36+
37+
if context.is_offline_mode():
38+
alembic_env_migration.run_migrations_offline(context) # type:ignore[arg-type]
39+
else:
40+
await alembic_env_migration.run_migrations_online(context) # type:ignore[arg-type]
41+
42+
43+
execute_coroutine_with_sync_worker(main())
44+
```
45+
46+
The EllarSQL migration package provides two main migration classes:
47+
48+
- **SingleDatabaseAlembicEnvMigration**: Manages migrations for a **single** database configuration, catering to both `Engine` and `AsyncEngine`.
49+
- **MultipleDatabaseAlembicEnvMigration**: Manages migrations for **multiple** database configurations, covering both `Engine` and `AsyncEngine`.
50+
51+
## **Customizing the Env file**
52+
53+
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.
54+
55+
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:
56+
57+
```python
58+
class AlembicEnvMigrationBase:
59+
def __init__(self, db_service: EllarSQLService) -> None:
60+
self.db_service = db_service
61+
self.use_two_phase = db_service.migration_options.use_two_phase
62+
63+
@abstractmethod
64+
def default_process_revision_directives(
65+
self,
66+
context: "MigrationContext",
67+
revision: RevisionArgs,
68+
directives: t.List["MigrationScript"],
69+
) -> t.Any:
70+
pass
71+
72+
@abstractmethod
73+
def run_migrations_offline(self, context: "EnvironmentContext") -> None:
74+
pass
75+
76+
@abstractmethod
77+
async def run_migrations_online(self, context: "EnvironmentContext") -> None:
78+
pass
79+
```
80+
81+
The `run_migrations_online` and `run_migrations_offline` are all similar to the same function from Alembic env.py template.
82+
The `default_process_revision_directives` is a callback is used to prevent an auto-migration from being generated
83+
when there are no changes to the schema described in details [here](http://alembic.zzzcomputing.com/en/latest/cookbook.html)
84+
85+
### Example
86+
```python
87+
import logging
88+
from logging.config import fileConfig
89+
90+
from alembic import context
91+
from ellar_sql.migrations import AlembicEnvMigrationBase
92+
from ellar_sql.model.database_binds import get_metadata
93+
from ellar.app import current_injector
94+
from ellar.threading import execute_coroutine_with_sync_worker
95+
from ellar_sql.services import EllarSQLService
96+
97+
# This is the Alembic Config object, which provides
98+
# access to the values within the .ini file in use.
99+
config = context.config
100+
logger = logging.getLogger("alembic.env")
101+
# Interpret the config file for Python logging.
102+
# This line sets up loggers essentially.
103+
fileConfig(config.config_file_name) # type:ignore[arg-type]
104+
105+
class MyCustomMigrationEnv(AlembicEnvMigrationBase):
106+
def default_process_revision_directives(
107+
self,
108+
context,
109+
revision,
110+
directives,
111+
) -> None:
112+
if getattr(context.config.cmd_opts, "autogenerate", False):
113+
script = directives[0]
114+
if script.upgrade_ops.is_empty():
115+
directives[:] = []
116+
logger.info("No changes in schema detected.")
117+
118+
def run_migrations_offline(self, context: "EnvironmentContext") -> None:
119+
"""Run migrations in 'offline' mode.
120+
121+
This configures the context with just a URL
122+
and not an Engine, though an Engine is acceptable
123+
here as well. By skipping the Engine creation
124+
we don't even need a DBAPI to be available.
125+
126+
Calls to context.execute() here emit the given string to the
127+
script output.
128+
129+
"""
130+
pass
131+
132+
async def run_migrations_online(self, context: "EnvironmentContext") -> None:
133+
"""Run migrations in 'online' mode.
134+
135+
In this scenario, we need to create an Engine
136+
and associate a connection with the context.
137+
138+
"""
139+
key, engine = self.db_service.engines.popitem()
140+
metadata = get_metadata(key, certain=True)
141+
142+
conf_args = {}
143+
conf_args.setdefault(
144+
"process_revision_directives", self.default_process_revision_directives
145+
)
146+
147+
with engine.connect() as connection:
148+
context.configure(
149+
connection=connection,
150+
target_metadata=metadata,
151+
**conf_args
152+
)
153+
154+
with context.begin_transaction():
155+
context.run_migrations()
156+
157+
async def main() -> None:
158+
db_service: EllarSQLService = current_injector.get(EllarSQLService)
159+
160+
# initialize migration class
161+
alembic_env_migration = MyCustomMigrationEnv(db_service)
162+
163+
if context.is_offline_mode():
164+
alembic_env_migration.run_migrations_offline(context)
165+
else:
166+
await alembic_env_migration.run_migrations_online(context)
167+
168+
execute_coroutine_with_sync_worker(main())
169+
```
170+
171+
This migration environment class, `MyCustomMigrationEnv`, inherits from `AlembicEnvMigrationBase`
172+
and provides the necessary methods for offline and online migrations.
173+
It utilizes the `EllarSQLService` to obtain the database engines and metadata for the migration process.
174+
The `main` function initializes and executes the migration class, with specific handling for offline and online modes.

0 commit comments

Comments
 (0)