{"id":16085719,"url":"https://github.com/febus982/sqlalchemy-bind-manager","last_synced_at":"2025-08-07T07:21:11.419Z","repository":{"id":65402408,"uuid":"568527300","full_name":"febus982/sqlalchemy-bind-manager","owner":"febus982","description":"Framework-agnostic SQLAlchemy bind manager and repository pattern implementation","archived":false,"fork":false,"pushed_at":"2025-03-03T17:01:07.000Z","size":2500,"stargazers_count":9,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-16T13:11:28.239Z","etag":null,"topics":["orm","python","repository-pattern","sqlalchemy","sqlalchemy-python"],"latest_commit_sha":null,"homepage":"https://febus982.github.io/sqlalchemy-bind-manager/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/febus982.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-20T20:01:07.000Z","updated_at":"2025-03-03T16:59:33.000Z","dependencies_parsed_at":"2024-01-27T01:32:03.598Z","dependency_job_id":"0a0ce629-a640-450f-8845-869cd48ff5f8","html_url":"https://github.com/febus982/sqlalchemy-bind-manager","commit_stats":{"total_commits":141,"total_committers":1,"mean_commits":141.0,"dds":0.0,"last_synced_commit":"878482fa548cc3ab4fc08162425780861d0c7df9"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/febus982%2Fsqlalchemy-bind-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/febus982%2Fsqlalchemy-bind-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/febus982%2Fsqlalchemy-bind-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/febus982%2Fsqlalchemy-bind-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/febus982","download_url":"https://codeload.github.com/febus982/sqlalchemy-bind-manager/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244077897,"owners_count":20394362,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["orm","python","repository-pattern","sqlalchemy","sqlalchemy-python"],"created_at":"2024-10-09T13:09:08.781Z","updated_at":"2025-08-07T07:21:11.407Z","avatar_url":"https://github.com/febus982.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 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\u0026logoColor=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://qlty.sh/badges/ed2ae1f6-fd5f-483b-8130-6f4f6518e31d/maintainability.svg)](https://qlty.sh/gh/febus982/projects/sqlalchemy-bind-manager)\n[![Code Coverage](https://qlty.sh/badges/ed2ae1f6-fd5f-483b-8130-6f4f6518e31d/test_coverage.svg)](https://qlty.sh/gh/febus982/projects/sqlalchemy-bind-manager)\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🚨 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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffebus982%2Fsqlalchemy-bind-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffebus982%2Fsqlalchemy-bind-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffebus982%2Fsqlalchemy-bind-manager/lists"}