Skip to content

Factory boy Support #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Documentation update
  • Loading branch information
eadwinCode committed Jan 23, 2024
commit a50ea41bebfecfa4df8fea48c272e606b8930bf6
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ EllarSQL Module adds support for `SQLAlchemy` and `Alembic` package to your Ella
$(venv) pip install ellar-sql
```

This library was inspired by [Flask-SQLAlchemy](https://flask-sqlalchemy.palletsprojects.com/en/3.1.x/){target="_blank"}

## Features

- Migration
Expand Down
1 change: 1 addition & 0 deletions docs/migrations/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
In the generated migration template, EllarSQL adopts an async-first approach for handling migration file generation.
This approach simplifies the execution of migrations for both `Session`, `Engine`, `AsyncSession`, and `AsyncEngine`,
but it also introduces a certain level of complexity.

```python
from logging.config import fileConfig

Expand Down
10 changes: 5 additions & 5 deletions docs/migrations/index.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# **Migrations**
EllarSQL also extends Alembic package
to add migration functionality to your application and make database operations available through EllarCLI commandline interface.
EllarSQL also extends **Alembic** package
to add migration functionality and make database operations accessible through **EllarCLI** commandline interface.

EllarSQL with Alembic does not override Alembic action rather provide Alembic all the configs/information
it needs to for a proper migration operation in your application.
**EllarSQL** with Alembic does not override Alembic action rather provide Alembic all the configs/information
it needs to for a proper migration/database operations.
Its also still possible to use Alembic outside EllarSQL setup when necessary.

This section is inspired by [`Flask Migrate`](https://flask-migrate.readthedocs.io/en/latest/#)

## **Quick Example**
We assume you have set up `EllarSQLModule` in your application and you have specified `migration_options`.
We assume you have set up `EllarSQLModule` in your application, and you have specified `migration_options`.

Create a simple `User` model as shown below:

Expand Down
38 changes: 20 additions & 18 deletions docs/models/configuration.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# **EllarSQLModule Config**
**`EllarSQLModule`** is an Ellar Dynamic Module that offers two ways of configuration:

**`EllarSQLModule`** is a versatile module, allowing configuration through the application `Config`.
This configuration can be done either using `EllarSQLModule.register_setup()` within your application setup or directly
during module registration with `EllarSQLModule.setup()`.
- `EllarSQLModule.register_setup()`: This method registers a `ModuleSetup` that depends on the application config.
- `EllarSQLModule.setup()`: This method immediately sets up the module with the provided options.

While examples have been provided for using `EllarSQLModule.setup()`, this section will shed light
on the usage of `EllarSQLModule.register_setup()`. Before diving into that, let's first explore
the setup options available for `EllarSQLModule`.
While we've explored many examples using `EllarSQLModule.setup()`, this section will focus on the usage of `EllarSQLModule.register_setup()`.

Before delving into that, let's first explore the setup options available for `EllarSQLModule`.
## **EllarSQLModule Configuration Parameters**

- **databases**: _typing.Union[str, typing.Dict[str, typing.Any]]_:
Expand Down Expand Up @@ -128,16 +127,17 @@ For more in-depth information
on [dealing with disconnects](https://docs.sqlalchemy.org/core/pooling.html#dealing-with-disconnects){target="_blank"},
refer to SQLAlchemy's documentation on handling connection issues.

## **EllarSQLModule With App Config**
As stated above, **EllarSQLModule** can be configured from application through `EllarSQLModule.register_setup`.
This will register a [ModuleSetup](https://python-ellar.github.io/ellar/basics/dynamic-modules/#modulesetup){target="_blank"} factory that checks for `ELLAR_SQL` in application config.
The value of `ELLAR_SQL` read from the application config will be passed `EllarSQLModule` setup action
which validates and initializes the module.
## **EllarSQLModule RegisterSetup**
As mentioned earlier, **EllarSQLModule** can be configured from the application through `EllarSQLModule.register_setup`.
This process registers a [ModuleSetup](https://python-ellar.github.io/ellar/basics/dynamic-modules/#modulesetup){target="_blank"} factory
that depends on the Application Config object.
The factory retrieves the `ELLAR_SQL` attribute from the config and validates the data before passing it to `EllarSQLModule` for setup.

It's important to note
that `ELLAR_SQL` will be a dictionary object with the above [configuration parameters](#ellarsqlmodule-configuration-parameters) as keys.
It's essential to note
that `ELLAR_SQL` will be a dictionary object with the [configuration parameters](#ellarsqlmodule-configuration-parameters)
mentioned above as keys.

A Quick example:
Here's a quick example:
```python title="db_learning/root_module.py"
from ellar.common import Module, exception_handler, IExecutionContext, JSONResponse, Response, IApplicationStartup
from ellar.app import App
Expand Down Expand Up @@ -180,8 +180,10 @@ class DevelopmentConfig(BaseConfig):
'models': []
}
```
We have added config for **EllarSQLModule** through `ELLAR_SQL`. And from there, the rest of the actions
will be the same as to normal registration with `EllarSQLModule.setup()`.
The registered ModuleSetup factory reads the `ELLAR_SQL` value and configures the `EllarSQLModule` appropriately.

But with this approach,
we can seamlessly change **EllarSQLModule** configuration in any environment like CI, Development, Staging or Production.
This approach is particularly useful when dealing with multiple environments.
It allows for seamless modification of the **ELLAR_SQL** values in various environments such as
Continuous Integration (CI), Development, Staging, or Production.
You can easily change the settings for each environment
and export the configurations as a string to be imported into `ELLAR_CONFIG_MODULE`.
39 changes: 19 additions & 20 deletions docs/models/index.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# **Quick Start**
In this section,
we shall go over how to set up **EllarSQL** in your ellar application
and have all necessary service registered and configuration set and ready to use.
In this segment, we will walk through the process of configuring **EllarSQL** within your Ellar application,
ensuring that all essential services are registered, configurations are set, and everything is prepared for immediate use.

Before we proceed, we assume you have a clear understanding
of how [Ellar Modules](https://python-ellar.github.io/ellar/basics/dynamic-modules/#module-dynamic-setup){target="_blank"} work.
Before we delve into the setup instructions, it is assumed that you possess a comprehensive
understanding of how [Ellar Modules](https://python-ellar.github.io/ellar/basics/dynamic-modules/#module-dynamic-setup){target="_blank"}
operate.

## **Installation**
Let's install all necessary packages. Also assuming your python environment has been configured:
Let us install all the required packages, assuming that your Python environment has been properly configured:

#### **For Existing Project:**
```shell
Expand Down Expand Up @@ -99,10 +99,10 @@ class UsersController(ecm.ControllerBase):
```

## **EllarSQLModule Setup**
In `root_module.py`, we need to do two things:
In the `root_module.py` file, two main tasks need to be performed:

- Register `UsersController` to have `/users` available when we start the application
- configure `EllarSQLModule` to configure and register all necessary services such as `EllarSQLService`, `Session` and `Engine`
1. Register the `UsersController` to make the `/users` endpoint available when starting the application.
2. Configure the `EllarSQLModule`, which will set up and register essential services such as `EllarSQLService`, `Session`, and `Engine`.

```python title="db_learning/root_module.py"
from ellar.common import Module, exception_handler, IExecutionContext, JSONResponse, Response, IApplicationStartup
Expand All @@ -111,7 +111,6 @@ from ellar.core import ModuleBase
from ellar_sql import EllarSQLModule, EllarSQLService
from .controller import UsersController


@Module(
modules=[EllarSQLModule.setup(
databases={
Expand All @@ -133,19 +132,19 @@ class ApplicationModule(ModuleBase, IApplicationStartup):
def exception_404_handler(cls, ctx: IExecutionContext, exc: Exception) -> Response:
return JSONResponse(dict(detail="Resource not found."), status_code=404)
```
In the above illustration,
we registered `UserController` and `EllarSQLModule` with some configurations on database and migration options.
See more on `EllarSQLModule` configurations.

Also, on the `on_startup` function, we retrieved `EllarSQLService` registered into the system through `EllarSQLModule`,
and call the `create_all()` function to create the SQLAlchemy tables.
In the provided code snippet:

- We registered `UserController` and `EllarSQLModule` with specific configurations for the database and migration options. For more details on [`EllarSQLModule` configurations](./configuration.md#ellarsqlmodule-config).

- In the `on_startup` method, we obtained the `EllarSQLService` from the Ellar Dependency Injection container using `EllarSQLModule`. Subsequently, we invoked the `create_all()` method to generate the necessary SQLAlchemy tables.

At this point, we are ready to test the application.
With these configurations, the application is now ready for testing.
```shell
ellar runserver --reload
```
Also,
remember to uncomment the `OpenAPIModule` configurations
in `server.py` to be able to visualize and interact with the `/users` the endpoint.
Additionally, please remember to uncomment the configurations for the `OpenAPIModule` in the `server.py`
file to enable visualization and interaction with the `/users` endpoint.

With that said, visit [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs){target="_blank"}
Once done,
you can access the OpenAPI documentation at [http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs){target="_blank"}.
75 changes: 43 additions & 32 deletions docs/models/models.md
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
# **Models and Tables**
`Model` from `ellar_sql.model.Model` package is a factory class for creating `SQLAlchemy` model.
It also manages a model database key and associates it to its Metadata and engine.
The `ellar_sql.model.Model` class acts as a factory for creating `SQLAlchemy` models, and
associating the generated models with the corresponding **Metadata** through their designated **`__database__`** key.

`Model` can be by defining `__base_config__` at the class level. This is necessary for a
case where we want to make a `Base` class that will be inherited through the application or change the declarative type
such as [DeclarativeBase](https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBase){target="_blank"} or
[DeclarativeBaseNoMeta](https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBaseNoMeta){target="_blank"}

`Model` configuration parameters:
This class can be configured through the `__base_config__` attribute, allowing you to specify how your `SQLAlchemy` model should be created.
The `__base_config__` attribute can be of type `ModelBaseConfig`, which is a dataclass, or a dictionary with keys that
match the attributes of `ModelBaseConfig`.

- **as_base**: Indicates if the class should be treated as a Base class for other model definitions. `Default: True`
- **use_base**: Indicates base classes that will be used to create a model base. `Default=[]`
Attributes of `ModelBaseConfig`:

- **as_base**: Indicates whether the class should be treated as a `Base` class for other model definitions, similar to creating a Base from a [DeclarativeBase](https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBase){target="_blank"} or [DeclarativeBaseNoMeta](https://docs.sqlalchemy.org/en/20/orm/mapping_api.html#sqlalchemy.orm.DeclarativeBaseNoMeta){target="_blank"} class. *(Default: False)*
- **use_base**: Specifies the base classes that will be used to create the `SQLAlchemy` model. *(Default: [])*

## **Base Class**
`Model` treats each model as a standalone model. This means each `model.Model` has a separate **declarative** base created for it
and the `__database__` key is used to determine its Metadata reference.
## **Creating a Base Class**
`Model` treats each model as a standalone entity. Each instance of `model.Model` creates a distinct **declarative** base for itself, using the `__database__` key as a reference to determine its associated **Metadata**. Consequently, models sharing the same `__database__` key will utilize the same **Metadata** object.

Let's explore how we can create a `Base` model using `Model`, similar to the approach in traditional `SQLAlchemy`.

Let's create a class with **DeclarativeBase**
```python
from ellar_sql import model, ModelBaseConfig


class Base(model.Model):
__base_config__ = ModelBaseConfig(as_base=True, use_bases=[model.DeclarativeBase])


assert issubclass(Base, model.DeclarativeBase)
```
If desired, you can enable [SQLAlchemy’s native support for data classes](https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#native-support-for-dataclasses-mapped-as-orm-models){target="_blank"}
by adding MappedAsDataclass as an additional parent class.

If you are interested in [SQLAlchemy’s native support for data classes](https://docs.sqlalchemy.org/en/20/changelog/whatsnew_20.html#native-support-for-dataclasses-mapped-as-orm-models){target="_blank"},
then you can add `MappedAsDataclass` to `use_bases` as shown below:

```python
from ellar_sql import model, ModelBaseConfig


class Base(model.Model):
__base_config__ = ModelBaseConfig(as_base=True, use_bases=[model.DeclarativeBase, model.MappedAsDataclass])

assert issubclass(Base, model.MappedAsDataclass)
```

Optionally, you have the flexibility to construct the SQLAlchemy object with a custom [`MetaData`](https://docs.sqlalchemy.org/en/20/core/metadata.html#sqlalchemy.schema.MetaData){target="_blank"} object.
This customization enables you to define a specific **naming convention** for constraints.
This becomes particularly valuable as it ensures consistency and predictability in constraint names.
This predictability proves especially beneficial when utilizing migrations, as detailed by [Alembic](https://alembic.sqlalchemy.org/en/latest/naming.html){target="_blank"}.
In the examples above, `Base` classes are created, all subclassed from the `use_bases` provided, and with the `as_base`
option, the factory creates the `Base` class as a `Base`.

## Create base with MetaData
You can also configure the SQLAlchemy object with a custom [`MetaData`](https://docs.sqlalchemy.org/en/20/core/metadata.html#sqlalchemy.schema.MetaData){target="_blank"} object.
For instance, you can define a specific **naming convention** for constraints, ensuring consistency and predictability in constraint names.
This can be particularly beneficial during migrations, as detailed by [Alembic](https://alembic.sqlalchemy.org/en/latest/naming.html){target="_blank"}.

For example:

```python
from ellar_sql import model, ModelBaseConfig
Expand All @@ -55,10 +65,12 @@ class Base(model.Model):
"pk": "pk_%(table_name)s"
})
```

## **Abstract Models and Mixins**
If the desired behavior is applicable only to specific models rather than all models, consider using an abstract model base class to customize only those models.
For instance, if certain models need to track their creation or update timestamps,
this approach allows for targeted customization.
If the desired behavior is only applicable to specific models rather than all models,
you can use an abstract model base class to customize only those models.
For example, if certain models need to track their creation or update **timestamps**, t
his approach allows for targeted customization.

```python
from datetime import datetime, timezone
Expand Down Expand Up @@ -98,9 +110,8 @@ class Book(model.Model, TimestampModel):
```

## **Defining Models**
Unlike plain SQLAlchemy, EllarSQL model will automatically generate a table name
if `__tablename__` is not set and a primary key column is defined.
This can be **disabled** by setting a value for `__tablename__` or defining `__tablename__` as a **declarative_attr**
Unlike plain SQLAlchemy, **EllarSQL** models will automatically generate a table name if the `__tablename__` attribute is not set,
provided a primary key column is defined.

```python
from ellar_sql import model
Expand All @@ -125,12 +136,12 @@ For a comprehensive guide on defining model classes declaratively, refer to
This resource provides detailed information and insights into the declarative approach for defining model classes.

## **Defining Tables**
The table class is designed to receive a table name, followed by columns and other table components such as constraints.
The table class is designed to receive a table name, followed by **columns** and other table **components** such as constraints.

EllarSQL enhances the functionality of the SQLAlchemy Table by facilitating the creation or
selection of metadata based on the `__database__` argument.
EllarSQL enhances the functionality of the SQLAlchemy Table
by facilitating the selection of **Metadata** based on the `__database__` argument.

Directly creating a table proves particularly valuable when establishing many-to-many relationships.
Directly creating a table proves particularly valuable when establishing **many-to-many** relationships.
In such cases, the association table doesn't need its dedicated model class; rather, it can be conveniently accessed
through the relevant relationship attributes on the associated models.

Expand All @@ -145,14 +156,13 @@ author_book_m2m = model.Table(
```

## **Quick Tutorial**
In this section, we'll delve into straightforward CRUD operations using the ORM objects.
In this section, we'll delve into straightforward **CRUD** operations using the ORM objects.
However, if you're not well-acquainted with SQLAlchemy,
feel free to explore their tutorial
on [ORM](https://docs.sqlalchemy.org/tutorial/orm_data_manipulation.html){target="_blank"}
for a more comprehensive understanding.

### **Create a Model**
Having understood, `Model` usage. Let's create a User model
Having understood, `Model` usage. Let's create a `User` model

```python
from ellar_sql import model
Expand Down Expand Up @@ -264,6 +274,7 @@ In the process of `EllarSQLModule` setup, three services are registered to the E
- `Session`SQLAlchemy Session of the default database configuration

Although with `EllarSQLService` you can get the `engine` and `session`. It's there for easy of access.

```python
import sqlalchemy as sa
import sqlalchemy.orm as sa_orm
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy