{"id":13688058,"url":"https://github.com/absent1706/sqlalchemy-mixins","last_synced_at":"2025-12-14T16:57:42.560Z","repository":{"id":18666329,"uuid":"84798035","full_name":"absent1706/sqlalchemy-mixins","owner":"absent1706","description":"Active Record, Django-like queries, nested eager load and beauty __repr__ for SQLAlchemy","archived":false,"fork":false,"pushed_at":"2024-09-08T01:52:33.000Z","size":297,"stargazers_count":783,"open_issues_count":28,"forks_count":69,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-09-22T19:34:31.722Z","etag":null,"topics":["activerecord","django-like","eager-load","eager-loading","orm","orm-extension","sqlalchemy"],"latest_commit_sha":null,"homepage":"","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/absent1706.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-13T07:45:31.000Z","updated_at":"2025-08-16T23:07:42.000Z","dependencies_parsed_at":"2024-06-18T13:49:59.941Z","dependency_job_id":"536f4679-f8f4-425e-bfc9-3b10a95519ca","html_url":"https://github.com/absent1706/sqlalchemy-mixins","commit_stats":{"total_commits":235,"total_committers":27,"mean_commits":8.703703703703704,"dds":0.5702127659574467,"last_synced_commit":"342c83e6ebd7563bb5b69cbfa5633b7f1027507d"},"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/absent1706/sqlalchemy-mixins","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent1706%2Fsqlalchemy-mixins","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent1706%2Fsqlalchemy-mixins/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent1706%2Fsqlalchemy-mixins/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent1706%2Fsqlalchemy-mixins/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/absent1706","download_url":"https://codeload.github.com/absent1706/sqlalchemy-mixins/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/absent1706%2Fsqlalchemy-mixins/sbom","scorecard":{"id":161803,"data":{"date":"2025-08-11","repo":{"name":"github.com/absent1706/sqlalchemy-mixins","commit":"9877efd7d4c934d91d25f0e7a3075852dabf3a53"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.8,"checks":[{"name":"Code-Review","score":4,"reason":"Found 7/16 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/pythonpublish.yml:1","Warn: no topLevel permission defined: .github/workflows/test-pr.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonpublish.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/absent1706/sqlalchemy-mixins/pythonpublish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pythonpublish.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/absent1706/sqlalchemy-mixins/pythonpublish.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-pr.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/absent1706/sqlalchemy-mixins/test-pr.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test-pr.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/absent1706/sqlalchemy-mixins/test-pr.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/pythonpublish.yml:18","Warn: pipCommand not pinned by hash: .github/workflows/pythonpublish.yml:19","Warn: pipCommand not pinned by hash: .github/workflows/test-pr.yml:24","Warn: pipCommand not pinned by hash: .github/workflows/test-pr.yml:25","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.txt:0","Info: FSF or OSI recognized license: MIT License: LICENSE.txt:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 25 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T13:41:02.276Z","repository_id":18666329,"created_at":"2025-08-16T13:41:02.277Z","updated_at":"2025-08-16T13:41:02.277Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27731968,"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","status":"online","status_checked_at":"2025-12-14T02:00:11.348Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["activerecord","django-like","eager-load","eager-loading","orm","orm-extension","sqlalchemy"],"created_at":"2024-08-02T15:01:05.781Z","updated_at":"2025-12-14T16:57:42.554Z","avatar_url":"https://github.com/absent1706.png","language":"Python","funding_links":[],"categories":["HarmonyOS","Python"],"sub_categories":["Windows Manager"],"readme":"![example workflow](https://github.com/absent1706/sqlalchemy-mixins/actions/workflows/test-pr.yml/badge.svg)\n[![PyPI version](https://img.shields.io/pypi/v/sqlalchemy_mixins.svg)](https://pypi.python.org/pypi/sqlalchemy_mixins)\n\n# SQLAlchemy mixins\n\n**Note**: As of **v1.3**, only python **3.5+** is supported.\n\nA pack of framework-agnostic, easy-to-integrate and well tested mixins for SQLAlchemy ORM.\n\nHeavily inspired by [Django ORM](https://docs.djangoproject.com/en/1.10/topics/db/queries/)\nand [Eloquent ORM](https://laravel.com/docs/5.4/eloquent)\n\nWhy it's cool:\n * framework-agnostic\n * easy integration to your existing project:\n   ```python\n    from sqlalchemy_mixins import AllFeaturesMixin\n\n    class User(Base, AllFeaturesMixin):\n         pass\n    ```\n * clean code, splitted by modules\n * follows best practices of\n    [Django ORM](https://docs.djangoproject.com/en/1.10/topics/db/queries/),\n    [Peewee](http://docs.peewee-orm.com/)\n    and [Eloquent ORM](https://laravel.com/docs/5.4/eloquent#retrieving-single-models),\n * 95%+ test coverage\n * already powers a big project\n\n\u003e Russian readers, see related **[article on habrahabr.ru](https://habrahabr.ru/post/324876/)**\n\n## Table of Contents\n\n1. [Installation](#installation)\n1. [Quick Start](#quick-start)\n    1. [Framework-agnostic](#framework-agnostic)\n    1. [Usage with Flask-SQLAlchemy](#usage-with-flask-sqlalchemy)\n1. [Features](#features)\n    1. [Active Record](#active-record)\n        1. [CRUD](#crud)\n        1. [Querying](#querying)\n    1. [Eager Load](#eager-load)\n    1. [Django-like queries](#django-like-queries)\n        1. [Filter and sort by relations](#filter-and-sort-by-relations)\n        1. [Automatic eager load relations](#automatic-eager-load-relations)\n    1. [All-in-one: smart_query](#all-in-one-smart_query)\n    1. [Beauty \\_\\_repr\\_\\_](#beauty-__repr__)\n    1. [Serialize to dict](#serialize-to-dict)\n    1. [Timestamps](#timestamps)\n1. [Internal architecture notes](#internal-architecture-notes)\n1. [Comparison with existing solutions](#comparison-with-existing-solutions)\n1. [Changelog](#changelog)\n\n## Installation\n\nUse pip\n```\npip install sqlalchemy_mixins\n```\n\nRun tests\n```\npython -m unittest discover sqlalchemy_mixins/\n```\n\n## Quick Start\n\n### Framework-agnostic\nHere's a quick demo of what our mixins can do.\n\n```python\nbob = User.create(name='Bob')\npost1 = Post.create(body='Post 1', user=bob, rating=3)\npost2 = Post.create(body='long-long-long-long-long body', rating=2,\n                    user=User.create(name='Bill'),\n                    comments=[Comment.create(body='cool!', user=bob)])\n\n# filter using operators like 'in' and 'contains' and relations like 'user'\n# will output this beauty: \u003cPost #1 body:'Post1' user:'Bill'\u003e\nprint(Post.where(rating__in=[2, 3, 4], user___name__like='%Bi%').all())\n# joinedload post and user\nprint(Comment.with_joined(Comment.user, Comment.post).first())\n# subqueryload posts\nprint(User.with_subquery(User.posts).first())\n# sort by rating DESC, user name ASC\nprint(Post.sort('-rating', 'user___name').all())\n# created_at, updated_at timestamps added automatically\nprint(\"Created Bob at \", bob.created_at)   \n# serialize to dict, with relationships\nprint(bob.to_dict(nested=True).all())\n```\n\n![icon](http://i.piccy.info/i9/c7168c8821f9e7023e32fd784d0e2f54/1489489664/1113/1127895/rsz_18_256.png)\nSee [full example](examples/all_features.py)\n\n\u003e To interactively play with this example from CLI, [install iPython](https://ipython.org/install.html) and type `ipython -i examples\\all_features.py`\n\n### Usage with Flask-SQLAlchemy\n\n```python\nimport sqlalchemy as sa\nfrom flask import Flask\nfrom flask_sqlalchemy import SQLAlchemy\nfrom sqlalchemy_mixins import AllFeaturesMixin\n\napp = Flask(__name__)\napp.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://'\ndb = SQLAlchemy(app)\n\n######### Models ######### \nclass BaseModel(db.Model, AllFeaturesMixin):\n    __abstract__ = True\n    pass\n\n\nclass User(BaseModel):\n    id = sa.Column(sa.Integer, primary_key=True)\n    name = sa.Column(sa.String)\n\n######## Initialize ########\nBaseModel.set_session(db.session)\n\n######## Create test entity ########\ndb.create_all()\nuser = User.create(name='bob')\nprint(user)\n```\n\n# *** Autocommit ***\nThis library relies on SQLAlchemy's `autocommit` flag. It needs to be set to True when initializing the session i.e:\n```python\nsession = scoped_session(sessionmaker(bind=engine, autocommit=True))\nBaseModel.set_session(session)\n```\nor with `Flask-SQLAlchemy`\n```python\ndb = SQLAlchemy(app, session_options={'autocommit': True})\n```\n\n# Features\n\nMain features are\n * [Active Record](#active-record)\n * [Eager Load](#eager-load)\n * [Django-like queries](#django-like-queries)\n * [Beauty \\_\\_repr\\_\\_](#beauty-__repr__)\n * [Timestamps](#timestamps)\n * [Serialize to dict](#serialize-to-dict)\n\n## Active Record\nprovided by [`ActiveRecordMixin`](sqlalchemy_mixins/activerecord.py)\n\nSQLAlchemy's [Data Mapper](https://en.wikipedia.org/wiki/Data_mapper_pattern)\npattern is cool, but\n[Active Record](https://en.wikipedia.org/wiki/Active_record_pattern)\npattern is easiest and more [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).\n\nWell, we implemented it on top of Data Mapper!\nAll we need is to just inject [session](http://docs.sqlalchemy.org/en/latest/orm/session.html) into ORM class while bootstrapping our app:\n\n```python\nBaseModel.set_session(session)\n# now we have access to BaseOrmModel.session property\n```\n\n### CRUD\nWe all love SQLAlchemy, but doing [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)\nis a bit tricky there.\n\nFor example, creating an object needs 3 lines of code:\n```python\nbob = User(name='Bobby', age=1)\nsession.add(bob)\nsession.flush()\n```\n\nWell, having access to session from model, we can just write\n```python\nbob = User.create(name='Bobby', age=1)\n```\nthat's how it's done in [Django ORM](https://docs.djangoproject.com/en/1.10/ref/models/querysets/#create)\nand [Peewee](http://docs.peewee-orm.com/en/latest/peewee/querying.html#creating-a-new-record)\n\nupdate and delete methods are provided as well\n```python\nbob.update(name='Bob', age=21)\nbob.delete()\n```\n\nAnd, as in [Django](https://docs.djangoproject.com/en/1.10/topics/db/queries/#retrieving-a-single-object-with-get)\nand [Eloquent](https://laravel.com/docs/5.4/eloquent#retrieving-single-models),\nwe can quickly retrieve object by id\n```python\nUser.find(1) # instead of session.query(User).get(1)\n```\n\nand fail if such id doesn't exist\n```python\nUser.find_or_fail(123987) # will raise sqlalchemy_mixins.ModelNotFoundError\n```\n\n![icon](http://i.piccy.info/i9/c7168c8821f9e7023e32fd784d0e2f54/1489489664/1113/1127895/rsz_18_256.png)\nSee [full example](examples/activerecord.py) and [tests](sqlalchemy_mixins/tests/test_activerecord.py)\n\n### Querying\nAs in [Flask-SQLAlchemy](http://flask-sqlalchemy.pocoo.org/2.1/queries/#querying-records),\n[Peewee](http://docs.peewee-orm.com/en/latest/peewee/api.html#Model.select)\nand [Django ORM](https://docs.djangoproject.com/en/1.10/topics/db/queries/#retrieving-objects),\nyou can quickly query some class\n```python\nUser.query # instead of session.query(User)\n```\n\nAlso we can quickly retrieve first or all objects:\n```python\nUser.first() # instead of session.query(User).first()\nUser.all() # instead of session.query(User).all()\n```\n\n![icon](http://i.piccy.info/i9/c7168c8821f9e7023e32fd784d0e2f54/1489489664/1113/1127895/rsz_18_256.png)\nSee [full example](examples/activerecord.py) and [tests](sqlalchemy_mixins/tests/test_activerecord.py)\n\n## Eager load\nprovided by [`EagerLoadMixin`](sqlalchemy_mixins/eagerload.py)\n\n### Nested eager load\nIf you use SQLAlchemy's [eager loading](http://docs.sqlalchemy.org/en/latest/orm/tutorial.html#eager-loading),\nyou may find it not very convenient, especially when we want, say,\nload user, all his posts and comments to every his post in the same query.\n\nWell, now you can easily set what ORM relations you want to eager load\n```python\nUser.with_({\n    User.posts: {\n        Post.comments: {\n            Comment.user: JOINED\n        }\n    }\n}).all()\n```\n\n### Subquery load\nSometimes we want to load relations in separate query, i.e. do [subqueryload](http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#sqlalchemy.orm.subqueryload).\nFor example, we load posts on page like [this](http://www.qopy.me/3V4Tsu_GTpCMJySzvVH1QQ),\nand for each post we want to have user and all comments (and comment authors).\n\nTo speed up query, we load comments in separate query, but, in this separate query, join user\n```python\nfrom sqlalchemy_mixins import JOINED, SUBQUERY\nPost.with_({\n    Post.user: JOINED, # joinedload user\n    Post.comments: (SUBQUERY, {  # load comments in separate query\n        Comment.user: JOINED  # but, in this separate query, join user\n    })\n}).all()\n```\n\nHere, posts will be loaded on first query, and comments with users - in second one.\nSee [SQLAlchemy docs](http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html)\nfor explaining relationship loading techniques.\n\n### Quick eager load\nFor simple cases, when you want to just \n[joinedload](http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#sqlalchemy.orm.joinedload)\nor [subqueryload](http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#sqlalchemy.orm.subqueryload) \na few relations, we have easier syntax for you:\n\n```python\nComment.with_joined(Comment.user, Comment.post).first()\nUser.with_subquery(User.posts).all()\n```\n\nSee [full example](examples/eagerload.py) and [tests](sqlalchemy_mixins/tests/test_eagerload.py)\n\n## Filter and sort by relations\nprovided by [`SmartQueryMixin`](sqlalchemy_mixins/smartquery.py)\n\n### Django-like queries\nWe implement Django-like\n[field lookups](https://docs.djangoproject.com/en/1.10/topics/db/queries/#field-lookups)\nand\n[automatic relation joins](https://docs.djangoproject.com/en/1.10/topics/db/queries/#lookups-that-span-relationships).\n\nIt means you can **filter and sort dynamically by attributes defined in strings!**\n\nSo, having defined `Post` model with `Post.user` relationship to `User` model,\nyou can write\n```python\nPost.where(rating__gt=2, user___name__like='%Bi%').all() # post rating \u003e 2 and post user name like ...\nPost.sort('-rating', 'user___name').all() # sort by rating DESC, user name ASC\n```\n(`___` splits relation and attribute, `__` splits attribute and operator)\n\n\u003e If you need more flexibility, you can use low-level `filter_expr` method `session.query(Post).filter(*Post.filter_expr(rating__gt=2, body='text'))`, [see example](examples/smartquery.py#L232).\n\u003e\n\u003e It's like [`filter_by` in SQLALchemy](http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.query.Query.filter_by), but also allows magic operators like `rating__gt`.\n\u003e\n\u003e Note: `filter_expr` method is very low-level and does NOT do magic Django-like joins. Use [`smart_query`](#all-in-one-smart_query) for that.\n\n\u003e **All relations used in filtering/sorting should be _explicitly set_, not just being a backref**\n\u003e\n\u003e In our example, `Post.user` relationship should be defined in `Post` class even if `User.posts` is defined too.\n\u003e\n\u003e So, you can't type\n\u003e ```python\n\u003e class User(BaseModel):\n\u003e     # ...\n\u003e     user = sa.orm.relationship('User', backref='posts')\n\u003e ```\n\u003e and skip defining `Post.user` relationship. You must define it anyway:\n\u003e\n\u003e ```python\n\u003e class Post(BaseModel):\n\u003e     # ...\n\u003e     user = sa.orm.relationship('User') # define it anyway\n\u003e ```\n\nFor DRY-ifying your code and incapsulating business logic, you can use\nSQLAlchemy's [hybrid attributes](http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html)\nand [hybrid_methods](http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html?highlight=hybrid_method#sqlalchemy.ext.hybrid.hybrid_method).\nUsing them in our filtering/sorting is straightforward (see examples and tests).\n\nSee [full example](examples/smartquery.py) and [tests](sqlalchemy_mixins/tests/test_smartquery.py)\n\n### Automatic eager load relations\nWell, as [`SmartQueryMixin`](sqlalchemy_mixins/smartquery.py) does auto-joins for filtering/sorting,\nthere's a sense to tell sqlalchemy that we already joined that relation.\n\nSo that relations are automatically set to be joinedload if they were used for filtering/sorting.\n\n\nSo, if we write\n```python\ncomments = Comment.where(post___public=True, post___user___name__like='Bi%').all()\n```\nthen no additional query will be executed if we will access used relations\n```python\ncomments[0].post\ncomments[0].post.user\n```\n\nCool, isn't it? =)\n\nSee [full example](examples/smartquery.py) and [tests](sqlalchemy_mixins/tests/test_smartquery.py)\n\n### All-in-one: smart_query\n#### Filter, sort and eager load in one smartest method.\nprovided by [`SmartQueryMixin`](sqlalchemy_mixins/smartquery.py)\n\nIn real world, we want to filter, sort and also eager load some relations at once.\nWell, if we use the same, say, `User.posts` relation in filtering and sorting,\nit **should not be joined twice**.\n\nThat's why we combined filter, sort and eager load in one smartest method:\n```python\nComment.smart_query(\n    filters={\n        'post___public': True,\n        'user__isnull': False\n    },\n    sort_attrs=['user___name', '-created_at'],\n    schema={\n        Comment.post: {\n            Post.user: JOINED\n        }\n    }).all()\n```\n\n\u003e ** New in 0.2.3 **\n\u003e In real world, you may need to \"smartly\" apply filters/sort/eagerload to any arbitrary query.\n\u003e And you can do this with standalone `smart_query` function:\n\u003e ```python\n\u003e smart_query(any_query, filters=...)\n\u003e ```\n\u003e It's especially useful for filtering/sorting/eagerloading [relations with lazy='dynamic'](http://docs.sqlalchemy.org/en/latest/orm/collections.html#dynamic-relationship)\n\u003e  for pages like [this](http://www.qopy.me/LwfSCu_ETM6At6el8wlbYA):\n\u003e ```python\n\u003e smart_query(user.comments_, filters=...)\n\u003e ```\n\u003e See [this example](examples/smartquery.py#L386)\n\n\n\u003e ** Experimental ** \n\u003e Additional logic (OR, AND, NOT etc) can be expressed using a nested structure for filters, with sqlalchemy operators (or any callable) as keys:\n\u003e ```\n\u003e from sqlalchemy import or_\n\u003e Comment.smart_query(filters={ or_: {\n\u003e     'post___public': True, \n\u003e     'user__isnull': False\n\u003e }})\n\u003e ```\n\u003e See [this example](examples/smartquery.py#L409) for more details\n\n\nSee [full example](examples/smartquery.py) and [tests](sqlalchemy_mixins/tests/test_smartquery.py)\n\n## Beauty \\_\\_repr\\_\\_\nprovided by [`ReprMixin`](sqlalchemy_mixins/repr.py)\n\nAs developers, we need to debug things with convenience.\nWhen we play in REPL, we can see this\n\n```\n\u003e\u003e\u003e session.query(Post).all()\n[\u003cmyapp.models.Post object at 0x04287A50\u003e, \u003cmyapp.models.Post object at 0x04287A90\u003e]\n```\n\nWell, using our mixin, we can have more readable output with post IDs:\n\n```\n\u003e\u003e\u003e session.query(Post).all()\n[\u003cPost #11\u003e, \u003cPost #12\u003e]\n```\n\nEven more, in `Post` model, we can define what else (except id) we want to see:\n\n```python\nclass User(BaseModel):\n    __repr_attrs__ = ['name']\n    # ...\n\n\nclass Post(BaseModel):\n    __repr_attrs__ = ['user', 'body'] # body is just column, user is relationship\n    # ...\n\n```\n\nNow we have\n```\n\u003e\u003e\u003e session.query(Post).all()\n[\u003cPost #11 user:\u003cUser #1 'Bill'\u003e body:'post 11'\u003e,\n \u003cPost #12 user:\u003cUser #2 'Bob'\u003e body:'post 12'\u003e]\n\n```\n\nLong attributes will be cut:\n\n```\nlong_post = Post(body='Post 2 long-long body', user=bob)\n\n\u003e\u003e\u003e long_post\n\u003cPost #2 body:'Post 2 long-lon...' user:\u003cUser #1 'Bob'\u003e\u003e\n```\n\nAnd you can customize max `__repr__` length:\n```\nclass Post(BaseModel):\n    # ...\n    __repr_max_length__ = 25\n    # ...\n    \n\u003e\u003e\u003e long_post\n\u003cPost #2 body:'Post 2 long-long body' user:\u003cUser #1 'Bob'\u003e\u003e   \n```\n\nSee [full example](examples/repr.py) and [tests](sqlalchemy_mixins/tests/test_repr.py)\n\n## Serialize to dict\nprovided by [`SerializeMixin`](sqlalchemy_mixins/serialize.py)\n\nYou can convert your model to dict.\n\n```python\n# 1. Without relationships\n#\n# {'id': 1, 'name': 'Bob'}\nprint(user.to_dict())\n\n# 2. With relationships\n#\n# {'id': 1,\n# 'name': 'Bob',\n# 'posts': [{'body': 'Post 1', 'id': 1, 'user_id': 1},\n#           {'body': 'Post 2', 'id': 2, 'user_id': 1}]}\nprint(user.to_dict(nested=True))\n```\nSee [full example](examples/serialize.py)\n\n## Timestamps\nprovided by [`TimestampsMixin`](sqlalchemy_mixins/timestamp.py)\n\nYou can view the created and updated timestamps.\n\n```python\nbob = User(name=\"Bob\")\nsession.add(bob)\nsession.flush()\n\nprint(\"Created Bob:    \", bob.created_at)\n# Created Bob:     2019-03-04 03:53:53.606765\n\nprint(\"Pre-update Bob: \", bob.updated_at)\n# Pre-update Bob:  2019-03-04 03:53:53.606769\n\ntime.sleep(2)\n\nbob.name = \"Robert\"\nsession.commit()\n\nprint(\"Updated Bob:    \", bob.updated_at)\n# Updated Bob:     2019-03-04 03:53:58.613044\n```\nSee [full example](examples/timestamp.py)\n\n# Internal architecture notes\nSome mixins re-use the same functionality. It lives in [`SessionMixin`](sqlalchemy_mixins/session.py) (session access) and [`InspectionMixin`](sqlalchemy_mixins/inspection.py) (inspecting columns, relations etc.) and other mixins inherit them.\n\nYou can use these mixins standalone if you want.\n\n# Comparison with existing solutions\nThere're a lot of extensions for SQLAlchemy, but most of them are not so universal.\n\n## Active record\nWe found several implementations of this pattern.\n\n**[ActiveAlchemy](https://github.com/mardix/active-alchemy/)**\n\nCool, but it forces you to use [their own way](https://github.com/mardix/active-alchemy/#create-the-model) to instantiate SQLAlchemy\nwhile to use [`ActiveRecordMixin`](sqlalchemy_mixins/activerecord.py) you should just make you model to inherit it.\n\n**[Flask-ActiveRecord](https://github.com/kofrasa/flask-activerecord)**\n\nCool, but tightly coupled with Flask.\n\n**[sqlalchemy-activerecord](https://github.com/deespater/sqlalchemy-activerecord)**\n\nFramework-agnostic, but lacks of functionality (only `save` method is provided) and Readme.\n\n## Django-like queries\nThere exists [sqlalchemy-django-query](https://github.com/mitsuhiko/sqlalchemy-django-query)\npackage which does similar things and it's really awesome.\n\nBut:\n * it doesn't [automatic eager load relations](#automatic-eager-load-relations)\n * it doesn't work with [hybrid attributes](http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html)\n   and [hybrid_methods](http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html?highlight=hybrid_method#sqlalchemy.ext.hybrid.hybrid_method)\n\n### Beauty \\_\\_repr\\_\\_\n[sqlalchemy-repr](https://github.com/manicmaniac/sqlalchemy-repr) already does this,\nbut there you can't choose which columns to output. It simply prints all columns, which can lead to too big output.\n\n# Changelog\n\n## v0.2\n\nMore clear methods in [`EagerLoadMixin`](sqlalchemy_mixins/eagerload.py):\n\n * *added* `with_subquery` method: it's like `with_joined`, but for [subqueryload](http://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#sqlalchemy.orm.subqueryload).\n   So you can now write:\n   \n   ```python\n   User.with_subquery('posts', 'comments').all()\n   ```  \n * `with_joined` method *arguments change*: instead of\n\n    ```python\n    Comment.with_joined(['user','post'])\n    ```\n\n    now simply write\n\n    ```python\n    Comment.with_joined('user','post')\n    ```\n\n * `with_` method *arguments change*: it now accepts *only dict schemas*. If you want to quickly joinedload relations, use `with_joined`\n * `with_dict` method *removed*. Instead, use `with_` method   \n\nOther changes in [`EagerLoadMixin`](sqlalchemy_mixins/eagerload.py):\n\n * constants *rename*: use cleaner `JOINED` and `SUBQUERY` instead of `JOINEDLOAD` and `SUBQUERYLOAD`\n * do not allow `None` in schema anymore, so instead of\n     ```python\n     Comment.with_({'user': None})\n     ```\n\n     write\n     ```python\n     Comment.with_({'user': JOINED})\n     ```\n\n### v0.2.1\n\nFix in [`InspectionMixin.columns`](sqlalchemy_mixins/inspection.py) property.\n\nIt didn't return columns inherited from other class. Now it works correct: \n\n```python\nclass Parent(BaseModel):\n    __tablename__ = 'parent'\n    id = sa.Column(sa.Integer, primary_key=True)\n\n\nclass Child(Parent):\n    some_prop = sa.Column(sa.String)\n    \nChild.columns # before it returned ['some_prop']\n              # now it returns ['id', 'some_prop'] \n```\n\n### v0.2.2\nFixed bug in [`ReprMixin`](sqlalchemy_mixins/repr.py): it [crashed](http://www.qopy.me/8UgySS2DTNOScdef_IuqAw) for objects without ID (newly created ones, not added yet to the session).\n\n### v0.2.3\n[`SmartQueryMixin`](sqlalchemy_mixins/smartquery.py): decoupled `smart_query` function from ORM classes\nso now you can use it with any query like\n\u003e ```python\n\u003e smart_query(any_query, filters=...)\n\u003e ```\nSee [description](#all-in-one-smart_query) (at the end of paragraph) and [example](examples/smartquery.py#L386)\n\n### v1.0.1\n\n1. Added [SerializationMixin](#serialize-to-dict) (thanks, [jonatasleon](https://github.com/jonatasleon))\n\n1. Added `ne` operator (thanks, [https://github.com/sophalch](sophalch)), so now you can write something like\n\n```python\nPost.where(rating__ne=2).all()\n```\n\n### v1.2\n\n\u003e This version contains breaking change, reverted in v1.2.1.\n\u003e So:\n\u003e   * v1.2 was removed from PyPi to avoid confusions\n\u003e   * for those who already downloaded v1.2, we hardly recommend to switch to 1.2.1.\n\u003e\n\u003e Just use [v1.2.1](#v121) instead\n\n\n\u003e By mistake, v1.2 code was released on PyPi as v1.1. \n\u003e It has been deleted from PyPi to avoid confusion. \n\u003e Sorry for any inconvenience guys.  \n\n1. **Removed Python 2, Python 3.2 compatibility**.\n\n1. Added Python 3.7, 3.8 compatibility. \n \n1. Added [TimestampsMixin](#timestamps) (thanks, [jonatasleon](https://github.com/jonatasleon)). \n\n1. (**Breaking change**, fixed in [v1.2.1](#v121)) [TimestampsMixin](#timestamps) was **included it to [AllFeaturesMixin](sqlalchemy_mixins/__init__.py)** which means `created_at` and `updated_at` fields were added to all models using `AllFeaturesMixin` which means you need to write migrations adding these fields.  \n\n1. Added [`contains` operator](https://github.com/absent1706/sqlalchemy-mixins/pull/29/files) (thanks, [alexbredo](https://github.com/alexbredo)).\n\n1. Added [date comparison operators](https://github.com/absent1706/sqlalchemy-mixins/pull/27/files) (thanks, [proteusvacuum](https://github.com/proteusvacuum)), so now you can write something like\n\n```python\nPost.where(created_at__year_ge=2014).all()\nPost.where(created_at__month_gt=10).all()\nPost.where(created_at__day_le=30).all()\n```\n\n### v1.2.1\n\nReverted breaking change introduced in [1.2](#v12):\n\nremoved [TimestampsMixin](#timestamps) from [AllFeaturesMixin](sqlalchemy_mixins/__init__.py). This addition in [v1.2](#v12) forced package users to write and run migration to add `created_at` and `updated_at` fields to all tables whose ORM models used `AllFeaturesMixin`.\n   Now you should add `TimestampsMixin` separately:\n   \n   ```python\n   class BaseModel(Base, AllFeaturesMixin, TimestampsMixin):\n       # ...\n   ```\n\n\n### v1.3\n\nAdd support for SQLAlchemy 1.4\n\n\n### v2.0.0\n\n\u003e This version contains breaking changes in multiple methods i.e methods that simplify\n\u003e eager loading. The use of strings while eager loading has been removed completely in SQLAlchemy 2.0.\n\u003e To much this behaviour, we have also removed the use of strings when eager loading\n\n1. Migrate to SQLAlchemy 2.0\n2. All methods in the `EagerLoadMixin` no longer accept strings. **Note** This means that you can only pass direct relationships.\n3. The `schema` parameter of the `smart_query` method/function no longer accepts string keys.\n4. **Dropped Python 3.6**\n5. Add Python 3.10 compatibility\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabsent1706%2Fsqlalchemy-mixins","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabsent1706%2Fsqlalchemy-mixins","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabsent1706%2Fsqlalchemy-mixins/lists"}