sqlalchemy-bind-manager


Namesqlalchemy-bind-manager JSON
Version 0.11.0 PyPI version JSON
download
home_pagehttps://febus982.github.io/sqlalchemy-bind-manager
SummaryA manager to easily handle multiple SQLAlchemy configurations
upload_time2024-12-15 15:33:18
maintainerNone
docs_urlNone
authorFederico Busetti
requires_python<3.14,>=3.9
licenseMIT
keywords sqlalchemy config manager
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # SQLAlchemy bind manager
![Static Badge](https://img.shields.io/badge/Python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13-blue?logo=python&logoColor=white)
[![Stable Version](https://img.shields.io/pypi/v/sqlalchemy-bind-manager?color=blue)](https://pypi.org/project/sqlalchemy-bind-manager/)
[![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta)

[![Python tests](https://github.com/febus982/sqlalchemy-bind-manager/actions/workflows/python-tests.yml/badge.svg?branch=main)](https://github.com/febus982/sqlalchemy-bind-manager/actions/workflows/python-tests.yml)
[![Maintainability](https://api.codeclimate.com/v1/badges/0140f7f4e559ae806887/maintainability)](https://codeclimate.com/github/febus982/sqlalchemy-bind-manager/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/0140f7f4e559ae806887/test_coverage)](https://codeclimate.com/github/febus982/sqlalchemy-bind-manager/test_coverage)

[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)
[![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)

This package provides an easy way to configure and use SQLAlchemy engines and sessions
without depending on frameworks.

It is composed by two main components:

* A manager class for SQLAlchemy engine and session configuration
* A repository/unit-of-work pattern implementation for model retrieval and persistence

## Installation

```bash
pip install sqlalchemy-bind-manager
```

## Components maturity

[//]: # (https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md)
* [![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta) **SQLAlchemy manager:** Implementation is mostly finalised, needs testing in production.
* [![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta) **Repository:** Implementation is mostly finalised, needs testing in production.
* [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#experimental) **Unit of work:** The implementation is working but limited to repositories using the same engine. Distributed transactions across different engines are not yet supported.

## Documentation

The complete documentation can be found [here](https://febus982.github.io/sqlalchemy-bind-manager)

## SQLAlchemy manager 

Initialise the manager providing an instance of `SQLAlchemyConfig`

```python
from sqlalchemy_bind_manager import SQLAlchemyConfig, SQLAlchemyBindManager

config = SQLAlchemyConfig(
    engine_url="sqlite:///./sqlite.db",
    engine_options=dict(connect_args={"check_same_thread": False}, echo=True),
    session_options=dict(expire_on_commit=False),
)

sa_manager = SQLAlchemyBindManager(config)
```

🚨 NOTE: Using global variables is not thread-safe, please read the [Documentation](https://febus982.github.io/sqlalchemy-bind-manager/manager/session/#note-on-multithreaded-applications) if your application uses multi-threading.

The `engine_url` and `engine_options` dictionaries accept the same parameters as SQLAlchemy [create_engine()](https://docs.sqlalchemy.org/en/14/core/engines.html#sqlalchemy.create_engine)

The `session_options` dictionary accepts the same parameters as SQLALchemy [sessionmaker()](https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker)

The `SQLAlchemyBindManager` provides some helper methods for common operations:

* `get_bind`: returns a `SQLAlchemyBind` or `SQLAlchemyAsyncBind` object
* `get_session`: returns a `Session` object, which works also as a context manager
* `get_mapper`: returns the mapper associated with the bind

Example:

```python
bind = sa_manager.get_bind()


class MyModel(bind.declarative_base):
    pass


# Persist an object
o = MyModel()
o.name = "John"
with sa_manager.get_session() as session:
    session.add(o)
    session.commit()
```

[Imperative model declaration](https://febus982.github.io/sqlalchemy-bind-manager/manager/models/) is also supported.

### Multiple database binds

`SQLAlchemyBindManager` accepts also multiple databases configuration, provided as a dictionary. The dictionary keys are used as a reference name for each bind.

```python
from sqlalchemy_bind_manager import SQLAlchemyConfig, SQLAlchemyBindManager

config = {
    "default": SQLAlchemyConfig(
        engine_url="sqlite:///./sqlite.db",
        engine_options=dict(connect_args={"check_same_thread": False}, echo=True),
        session_options=dict(expire_on_commit=False),
    ),
    "secondary": SQLAlchemyConfig(
        engine_url="sqlite:///./secondary.db",
        engine_options=dict(connect_args={"check_same_thread": False}, echo=True),
        session_options=dict(expire_on_commit=False),
    ),
}

sa_manager = SQLAlchemyBindManager(config)
```

All the `SQLAlchemyBindManager` helper methods accept the `bind_name` optional parameter:

* `get_bind(bind_name="default")`: returns a `SQLAlchemyBind` or `SQLAlchemyAsyncBind` object
* `get_session(bind_name="default")`: returns a `Session` or `AsyncSession` object, which works also as a context manager
* `get_mapper(bind_name="default")`: returns the mapper associated with the bind

### Asynchronous database binds

Is it possible to supply configurations for asyncio supported engines.

```python
config = SQLAlchemyAsyncConfig(
    engine_url="postgresql+asyncpg://scott:tiger@localhost/test",
)
```

This will make sure we have an `AsyncEngine` and an `AsyncSession` are initialised, as an asynchronous context manager.

```python
async with sa_manager.get_session() as session:
    session.add(o)
    await session.commit()
```

Note that async implementation has several differences from the sync one, make sure
to check [SQLAlchemy asyncio documentation](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html)

## Repository / Unit of work

The `SQLAlchemyRepository` and `SQLAlchemyAsyncRepository` class can be used directly or by extending them.

```python
from sqlalchemy_bind_manager.repository import SQLAlchemyRepository


class MyModel(declarative_base):
    pass

# Direct usage
repo_instance = SQLAlchemyRepository(sqlalchemy_bind_manager.get_bind(), model_class=MyModel)

class ModelRepository(SQLAlchemyRepository[MyModel]):
    _model = MyModel
    
    def _some_custom_method_implemented(self):
        ...

# Extended class usage
extended_repo_instance = ModelRepository(sqlalchemy_bind_manager.get_bind())
```

The repository classes provides methods for  common use case:

* `get`: Retrieve a model by primary key
* `save`: Persist a model
* `save_many`: Persist multiple models in a single transaction
* `delete`: Delete a model
* `find`: Search for a list of models (basically an adapter for SELECT queries)
* `paginated_find`: Search for a list of models, with pagination support
* `cursor_paginated_find`: Search for a list of models, with cursor based pagination support

### Use the Unit Of Work to share a session among multiple repositories

It is possible we need to run several operations in a single database transaction. While a single
repository provide by itself an isolated session for single operations, we have to use a different
approach for multiple operations.

We can use the `UnitOfWork` or the `AsyncUnitOfWork` class to provide a shared session to
be used for repository operations, **assumed the same bind is used for all the repositories**.

```python
class MyRepo(SQLAlchemyRepository):
    _model = MyModel

bind = sa_manager.get_bind()
uow = UnitOfWork(bind)
uow.register_repository("repo_a", MyRepo)
uow.register_repository("repo_b", SQLAlchemyRepository, MyOtherModel)

with uow.transaction():
    uow.repository("repo_a").save(some_model)
    uow.repository("repo_b").save(some_other_model)
```


            

Raw data

            {
    "_id": null,
    "home_page": "https://febus982.github.io/sqlalchemy-bind-manager",
    "name": "sqlalchemy-bind-manager",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<3.14,>=3.9",
    "maintainer_email": null,
    "keywords": "sqlalchemy, config, manager",
    "author": "Federico Busetti",
    "author_email": "729029+febus982@users.noreply.github.com",
    "download_url": "https://files.pythonhosted.org/packages/69/44/19a0ba44a615728de26ba30bd9a971fbe0866591ce855ecd78d6e064092e/sqlalchemy_bind_manager-0.11.0.tar.gz",
    "platform": null,
    "description": "# SQLAlchemy bind manager\n![Static Badge](https://img.shields.io/badge/Python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13-blue?logo=python&logoColor=white)\n[![Stable Version](https://img.shields.io/pypi/v/sqlalchemy-bind-manager?color=blue)](https://pypi.org/project/sqlalchemy-bind-manager/)\n[![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta)\n\n[![Python tests](https://github.com/febus982/sqlalchemy-bind-manager/actions/workflows/python-tests.yml/badge.svg?branch=main)](https://github.com/febus982/sqlalchemy-bind-manager/actions/workflows/python-tests.yml)\n[![Maintainability](https://api.codeclimate.com/v1/badges/0140f7f4e559ae806887/maintainability)](https://codeclimate.com/github/febus982/sqlalchemy-bind-manager/maintainability)\n[![Test Coverage](https://api.codeclimate.com/v1/badges/0140f7f4e559ae806887/test_coverage)](https://codeclimate.com/github/febus982/sqlalchemy-bind-manager/test_coverage)\n\n[![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg)](https://mypy-lang.org/)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v1.json)](https://github.com/charliermarsh/ruff)\n[![security: bandit](https://img.shields.io/badge/security-bandit-yellow.svg)](https://github.com/PyCQA/bandit)\n\nThis package provides an easy way to configure and use SQLAlchemy engines and sessions\nwithout depending on frameworks.\n\nIt is composed by two main components:\n\n* A manager class for SQLAlchemy engine and session configuration\n* A repository/unit-of-work pattern implementation for model retrieval and persistence\n\n## Installation\n\n```bash\npip install sqlalchemy-bind-manager\n```\n\n## Components maturity\n\n[//]: # (https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md)\n* [![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta) **SQLAlchemy manager:** Implementation is mostly finalised, needs testing in production.\n* [![stability-beta](https://img.shields.io/badge/stability-beta-33bbff.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#beta) **Repository:** Implementation is mostly finalised, needs testing in production.\n* [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)](https://github.com/mkenney/software-guides/blob/master/STABILITY-BADGES.md#experimental) **Unit of work:** The implementation is working but limited to repositories using the same engine. Distributed transactions across different engines are not yet supported.\n\n## Documentation\n\nThe complete documentation can be found [here](https://febus982.github.io/sqlalchemy-bind-manager)\n\n## SQLAlchemy manager \n\nInitialise the manager providing an instance of `SQLAlchemyConfig`\n\n```python\nfrom sqlalchemy_bind_manager import SQLAlchemyConfig, SQLAlchemyBindManager\n\nconfig = SQLAlchemyConfig(\n    engine_url=\"sqlite:///./sqlite.db\",\n    engine_options=dict(connect_args={\"check_same_thread\": False}, echo=True),\n    session_options=dict(expire_on_commit=False),\n)\n\nsa_manager = SQLAlchemyBindManager(config)\n```\n\n\ud83d\udea8 NOTE: Using global variables is not thread-safe, please read the [Documentation](https://febus982.github.io/sqlalchemy-bind-manager/manager/session/#note-on-multithreaded-applications) if your application uses multi-threading.\n\nThe `engine_url` and `engine_options` dictionaries accept the same parameters as SQLAlchemy [create_engine()](https://docs.sqlalchemy.org/en/14/core/engines.html#sqlalchemy.create_engine)\n\nThe `session_options` dictionary accepts the same parameters as SQLALchemy [sessionmaker()](https://docs.sqlalchemy.org/en/14/orm/session_api.html#sqlalchemy.orm.sessionmaker)\n\nThe `SQLAlchemyBindManager` provides some helper methods for common operations:\n\n* `get_bind`: returns a `SQLAlchemyBind` or `SQLAlchemyAsyncBind` object\n* `get_session`: returns a `Session` object, which works also as a context manager\n* `get_mapper`: returns the mapper associated with the bind\n\nExample:\n\n```python\nbind = sa_manager.get_bind()\n\n\nclass MyModel(bind.declarative_base):\n    pass\n\n\n# Persist an object\no = MyModel()\no.name = \"John\"\nwith sa_manager.get_session() as session:\n    session.add(o)\n    session.commit()\n```\n\n[Imperative model declaration](https://febus982.github.io/sqlalchemy-bind-manager/manager/models/) is also supported.\n\n### Multiple database binds\n\n`SQLAlchemyBindManager` accepts also multiple databases configuration, provided as a dictionary. The dictionary keys are used as a reference name for each bind.\n\n```python\nfrom sqlalchemy_bind_manager import SQLAlchemyConfig, SQLAlchemyBindManager\n\nconfig = {\n    \"default\": SQLAlchemyConfig(\n        engine_url=\"sqlite:///./sqlite.db\",\n        engine_options=dict(connect_args={\"check_same_thread\": False}, echo=True),\n        session_options=dict(expire_on_commit=False),\n    ),\n    \"secondary\": SQLAlchemyConfig(\n        engine_url=\"sqlite:///./secondary.db\",\n        engine_options=dict(connect_args={\"check_same_thread\": False}, echo=True),\n        session_options=dict(expire_on_commit=False),\n    ),\n}\n\nsa_manager = SQLAlchemyBindManager(config)\n```\n\nAll the `SQLAlchemyBindManager` helper methods accept the `bind_name` optional parameter:\n\n* `get_bind(bind_name=\"default\")`: returns a `SQLAlchemyBind` or `SQLAlchemyAsyncBind` object\n* `get_session(bind_name=\"default\")`: returns a `Session` or `AsyncSession` object, which works also as a context manager\n* `get_mapper(bind_name=\"default\")`: returns the mapper associated with the bind\n\n### Asynchronous database binds\n\nIs it possible to supply configurations for asyncio supported engines.\n\n```python\nconfig = SQLAlchemyAsyncConfig(\n    engine_url=\"postgresql+asyncpg://scott:tiger@localhost/test\",\n)\n```\n\nThis will make sure we have an `AsyncEngine` and an `AsyncSession` are initialised, as an asynchronous context manager.\n\n```python\nasync with sa_manager.get_session() as session:\n    session.add(o)\n    await session.commit()\n```\n\nNote that async implementation has several differences from the sync one, make sure\nto check [SQLAlchemy asyncio documentation](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html)\n\n## Repository / Unit of work\n\nThe `SQLAlchemyRepository` and `SQLAlchemyAsyncRepository` class can be used directly or by extending them.\n\n```python\nfrom sqlalchemy_bind_manager.repository import SQLAlchemyRepository\n\n\nclass MyModel(declarative_base):\n    pass\n\n# Direct usage\nrepo_instance = SQLAlchemyRepository(sqlalchemy_bind_manager.get_bind(), model_class=MyModel)\n\nclass ModelRepository(SQLAlchemyRepository[MyModel]):\n    _model = MyModel\n    \n    def _some_custom_method_implemented(self):\n        ...\n\n# Extended class usage\nextended_repo_instance = ModelRepository(sqlalchemy_bind_manager.get_bind())\n```\n\nThe repository classes provides methods for  common use case:\n\n* `get`: Retrieve a model by primary key\n* `save`: Persist a model\n* `save_many`: Persist multiple models in a single transaction\n* `delete`: Delete a model\n* `find`: Search for a list of models (basically an adapter for SELECT queries)\n* `paginated_find`: Search for a list of models, with pagination support\n* `cursor_paginated_find`: Search for a list of models, with cursor based pagination support\n\n### Use the Unit Of Work to share a session among multiple repositories\n\nIt is possible we need to run several operations in a single database transaction. While a single\nrepository provide by itself an isolated session for single operations, we have to use a different\napproach for multiple operations.\n\nWe can use the `UnitOfWork` or the `AsyncUnitOfWork` class to provide a shared session to\nbe used for repository operations, **assumed the same bind is used for all the repositories**.\n\n```python\nclass MyRepo(SQLAlchemyRepository):\n    _model = MyModel\n\nbind = sa_manager.get_bind()\nuow = UnitOfWork(bind)\nuow.register_repository(\"repo_a\", MyRepo)\nuow.register_repository(\"repo_b\", SQLAlchemyRepository, MyOtherModel)\n\nwith uow.transaction():\n    uow.repository(\"repo_a\").save(some_model)\n    uow.repository(\"repo_b\").save(some_other_model)\n```\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A manager to easily handle multiple SQLAlchemy configurations",
    "version": "0.11.0",
    "project_urls": {
        "Homepage": "https://febus982.github.io/sqlalchemy-bind-manager",
        "Repository": "https://github.com/febus982/sqlalchemy-bind-manager"
    },
    "split_keywords": [
        "sqlalchemy",
        " config",
        " manager"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "b65efb76f6b877a075eb6a302bfbbffd7cb8bae069533b7fafeea0441da79240",
                "md5": "4d55041b304b77a06438e9718de753c7",
                "sha256": "5592084f6e6cfd834960f375e1ca691722f062feaad521095f87d72e0d594141"
            },
            "downloads": -1,
            "filename": "sqlalchemy_bind_manager-0.11.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "4d55041b304b77a06438e9718de753c7",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<3.14,>=3.9",
            "size": 30195,
            "upload_time": "2024-12-15T15:33:14",
            "upload_time_iso_8601": "2024-12-15T15:33:14.828660Z",
            "url": "https://files.pythonhosted.org/packages/b6/5e/fb76f6b877a075eb6a302bfbbffd7cb8bae069533b7fafeea0441da79240/sqlalchemy_bind_manager-0.11.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "694419a0ba44a615728de26ba30bd9a971fbe0866591ce855ecd78d6e064092e",
                "md5": "9008e6201f67fac66d85fa173d3953a6",
                "sha256": "f84767d80a3aca5cb8b29de0c38d95368799dcc74a3b5c9d0ba303757d4300e8"
            },
            "downloads": -1,
            "filename": "sqlalchemy_bind_manager-0.11.0.tar.gz",
            "has_sig": false,
            "md5_digest": "9008e6201f67fac66d85fa173d3953a6",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<3.14,>=3.9",
            "size": 18455,
            "upload_time": "2024-12-15T15:33:18",
            "upload_time_iso_8601": "2024-12-15T15:33:18.493014Z",
            "url": "https://files.pythonhosted.org/packages/69/44/19a0ba44a615728de26ba30bd9a971fbe0866591ce855ecd78d6e064092e/sqlalchemy_bind_manager-0.11.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-12-15 15:33:18",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "febus982",
    "github_project": "sqlalchemy-bind-manager",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "tox": true,
    "lcname": "sqlalchemy-bind-manager"
}
        
Elapsed time: 0.40692s