{"id":13665339,"url":"https://github.com/juliotrigo/sqlalchemy-filters","last_synced_at":"2026-02-22T02:38:44.156Z","repository":{"id":34296983,"uuid":"63061283","full_name":"juliotrigo/sqlalchemy-filters","owner":"juliotrigo","description":"Filter, sort and paginate SQLAlchemy query objects. Ideal for exposing these actions over a REST API.","archived":false,"fork":false,"pushed_at":"2025-01-24T16:24:28.000Z","size":228,"stargazers_count":343,"open_issues_count":23,"forks_count":81,"subscribers_count":40,"default_branch":"master","last_synced_at":"2025-11-27T22:13:49.647Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/juliotrigo.png","metadata":{"files":{"readme":"README.rst","changelog":"CHANGELOG.rst","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2016-07-11T11:11:18.000Z","updated_at":"2025-11-25T00:07:59.000Z","dependencies_parsed_at":"2022-08-08T00:15:41.522Z","dependency_job_id":"07a74cf0-8956-4bd3-992c-2d0d73dc4ea6","html_url":"https://github.com/juliotrigo/sqlalchemy-filters","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/juliotrigo/sqlalchemy-filters","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliotrigo%2Fsqlalchemy-filters","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliotrigo%2Fsqlalchemy-filters/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliotrigo%2Fsqlalchemy-filters/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliotrigo%2Fsqlalchemy-filters/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/juliotrigo","download_url":"https://codeload.github.com/juliotrigo/sqlalchemy-filters/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/juliotrigo%2Fsqlalchemy-filters/sbom","scorecard":{"id":542297,"data":{"date":"2025-08-11","repo":{"name":"github.com/juliotrigo/sqlalchemy-filters","commit":"e03b1aeb6b7a4f7709a7a57d6ca5d302a719b9fa"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.5,"checks":[{"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":"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":"Code-Review","score":7,"reason":"Found 3/4 approved changesets -- score normalized to 7","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":"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/tests.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":"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/tests.yml:65: update your workflow using https://app.stepsecurity.io/secureworkflow/juliotrigo/sqlalchemy-filters/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:66: update your workflow using https://app.stepsecurity.io/secureworkflow/juliotrigo/sqlalchemy-filters/tests.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:71","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   1 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":"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":"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":"License","score":9,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Warn: project license file does not contain an FSF or OSI license."],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"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":"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":"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":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 29 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-20T08:31:12.110Z","repository_id":34296983,"created_at":"2025-08-20T08:31:12.110Z","updated_at":"2025-08-20T08:31:12.110Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29703904,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T23:35:04.139Z","status":"online","status_checked_at":"2026-02-22T02:00:08.193Z","response_time":110,"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":[],"created_at":"2024-08-02T06:00:33.022Z","updated_at":"2026-02-22T02:38:44.114Z","avatar_url":"https://github.com/juliotrigo.png","language":"Python","funding_links":[],"categories":["Credits","Python"],"sub_categories":[],"readme":"SQLAlchemy filters\n==================\n\n.. pull-quote::\n\n    Filter, sort and paginate SQLAlchemy query objects. Ideal for\n    exposing these actions over a REST API.\n\n\n.. image:: https://img.shields.io/pypi/v/sqlalchemy-filters.svg\n    :target: https://pypi.org/project/sqlalchemy-filters/\n\n.. image:: https://img.shields.io/pypi/pyversions/sqlalchemy-filters.svg\n    :target: https://pypi.org/project/sqlalchemy-filters/\n\n.. image:: https://img.shields.io/pypi/format/sqlalchemy-filters.svg\n    :target: https://pypi.org/project/sqlalchemy-filters/\n\n.. image:: https://github.com/juliotrigo/sqlalchemy-filters/actions/workflows/tests.yml/badge.svg\n    :target: https://github.com/juliotrigo/sqlalchemy-filters/actions\n\n\nFiltering\n---------\n\nAssuming that we have a SQLAlchemy_ ``query`` object:\n\n.. code-block:: python\n\n    from sqlalchemy import Column, Integer, String\n    from sqlalchemy.ext.declarative import declarative_base\n\n\n    class Base(object):\n        id = Column(Integer, primary_key=True)\n        name = Column(String(50), nullable=False)\n        count = Column(Integer, nullable=True)\n\n        @hybrid_property\n        def count_square(self):\n            return self.count * self.count\n\n        @hybrid_method\n        def three_times_count(self):\n            return self.count * 3\n\n\n    Base = declarative_base(cls=Base)\n\n\n    class Foo(Base):\n\n        __tablename__ = 'foo'\n\n    # ...\n\n    query = session.query(Foo)\n\nThen we can apply filters to that ``query`` object (multiple times):\n\n.. code-block:: python\n\n    from sqlalchemy_filters import apply_filters\n\n\n    # `query` should be a SQLAlchemy query object\n\n    filter_spec = [{'field': 'name', 'op': '==', 'value': 'name_1'}]\n    filtered_query = apply_filters(query, filter_spec)\n\n    more_filters = [{'field': 'foo_id', 'op': 'is_not_null'}]\n    filtered_query = apply_filters(filtered_query, more_filters)\n\n    result = filtered_query.all()\n\nIt is also possible to filter queries that contain multiple models,\nincluding joins:\n\n.. code-block:: python\n\n    class Bar(Base):\n\n        __tablename__ = 'bar'\n\n        foo_id = Column(Integer, ForeignKey('foo.id'))\n\n\n.. code-block:: python\n\n    query = session.query(Foo).join(Bar)\n\n    filter_spec = [\n        {'model': 'Foo', 'field': 'name', 'op': '==', 'value': 'name_1'},\n        {'model': 'Bar', 'field': 'count', 'op': '\u003e=', 'value': 5},\n    ]\n    filtered_query = apply_filters(query, filter_spec)\n\n    result = filtered_query.all()\n\n\n``apply_filters`` will attempt to automatically join models to ``query``\nif they're not already present and a model-specific filter is supplied.\nFor example, the value of ``filtered_query`` in the following two code\nblocks is identical:\n\n.. code-block:: python\n\n    query = session.query(Foo).join(Bar)  # join pre-applied to query\n\n    filter_spec = [\n        {'model': 'Foo', 'field': 'name', 'op': '==', 'value': 'name_1'},\n        {'model': 'Bar', 'field': 'count', 'op': '\u003e=', 'value': 5},\n    ]\n    filtered_query = apply_filters(query, filter_spec)\n\n.. code-block:: python\n\n    query = session.query(Foo)  # join to Bar will be automatically applied\n\n    filter_spec = [\n        {'field': 'name', 'op': '==', 'value': 'name_1'},\n        {'model': 'Bar', 'field': 'count', 'op': '\u003e=', 'value': 5},\n    ]\n    filtered_query = apply_filters(query, filter_spec)\n\nThe automatic join is only possible if SQLAlchemy_ can implictly\ndetermine the condition for the join, for example because of a foreign\nkey relationship.\n\nAutomatic joins allow flexibility for clients to filter and sort by related\nobjects without specifying all possible joins on the server beforehand. Feature\ncan be explicitly disabled by passing ``do_auto_join=False`` argument to the\n``apply_filters`` call.\n\nNote that first filter of the second block does not specify a model.\nIt is implictly applied to the ``Foo`` model because that is the only\nmodel in the original query passed to ``apply_filters``.\n\nIt is also possible to apply filters to queries defined by fields, functions or\n``select_from`` clause:\n\n.. code-block:: python\n\n    query_alt_1 = session.query(Foo.id, Foo.name)\n    query_alt_2 = session.query(func.count(Foo.id))\n    query_alt_3 = session.query().select_from(Foo).add_column(Foo.id)\n\nHybrid attributes\n^^^^^^^^^^^^^^^^^\n\nYou can filter by a `hybrid attribute`_: a `hybrid property`_ or a `hybrid method`_.\n\n.. code-block:: python\n\n    query = session.query(Foo)\n\n    filter_spec = [{'field': 'count_square', 'op': '\u003e=', 'value': 25}]\n    filter_spec = [{'field': 'three_times_count', 'op': '\u003e=', 'value': 15}]\n\n    filtered_query = apply_filters(query, filter_spec)\n    result = filtered_query.all()\n\n\nRestricted Loads\n----------------\n\nYou can restrict the fields that SQLAlchemy_ loads from the database by\nusing the ``apply_loads`` function:\n\n.. code-block:: python\n\n    query = session.query(Foo, Bar).join(Bar)\n    load_spec = [\n        {'model': 'Foo', 'fields': ['name']},\n        {'model': 'Bar', 'fields': ['count']}\n    ]\n    query = apply_loads(query, load_spec)  # will load only Foo.name and Bar.count\n\n\nThe effect of the ``apply_loads`` function is to ``_defer_`` the load\nof any other fields to when/if they're accessed, rather than loading\nthem when the query is executed. It only applies to fields that would be\nloaded during normal query execution.\n\n\nEffect on joined queries\n^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe default SQLAlchemy_ join is lazy, meaning that columns from the\njoined table are loaded only when required. Therefore ``apply_loads``\nhas limited effect in the following scenario:\n\n.. code-block:: python\n\n    query = session.query(Foo).join(Bar)\n    load_spec = [\n        {'model': 'Foo', 'fields': ['name']}\n        {'model': 'Bar', 'fields': ['count']}  # ignored\n    ]\n    query = apply_loads(query, load_spec)  # will load only Foo.name\n\n\n``apply_loads`` cannot be applied to columns that are loaded as\n`joined eager loads \u003chttp://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#joined-eager-loading\u003e`_.\nThis is because a joined eager load does not add the joined model to the\noriginal query, as explained\n`here \u003chttp://docs.sqlalchemy.org/en/latest/orm/loading_relationships.html#the-zen-of-joined-eager-loading\u003e`_\n\nThe following would not prevent all columns from ``Bar`` being eagerly\nloaded:\n\n.. code-block:: python\n\n    query = session.query(Foo).options(joinedload(Foo.bar))\n    load_spec = [\n        {'model': 'Foo', 'fields': ['name']}\n        {'model': 'Bar', 'fields': ['count']}\n    ]\n    query = apply_loads(query, load_spec)\n\n.. sidebar:: Automatic Join\n\n    In fact, what happens here is that ``Bar`` is automatically joined\n    to ``query``, because it is determined that ``Bar`` is not part of\n    the original query. The ``load_spec`` therefore has no effect\n    because the automatic join results in lazy evaluation.\n\nIf you wish to perform a joined load with restricted columns, you must\nspecify the columns as part of the joined load, rather than with\n``apply_loads``:\n\n.. code-block:: python\n\n    query = session.query(Foo).options(joinedload(Bar).load_only('count'))\n    load_spec = [\n        {'model': 'Foo', 'fields': ['name']}\n    ]\n    query = apply_loads(query. load_spec)  # will load ony Foo.name and Bar.count\n\n\nSort\n----\n\n.. code-block:: python\n\n    from sqlalchemy_filters import apply_sort\n\n\n    # `query` should be a SQLAlchemy query object\n\n    sort_spec = [\n        {'model': 'Foo', 'field': 'name', 'direction': 'asc'},\n        {'model': 'Bar', 'field': 'id', 'direction': 'desc'},\n    ]\n    sorted_query = apply_sort(query, sort_spec)\n\n    result = sorted_query.all()\n\n\n``apply_sort`` will attempt to automatically join models to ``query`` if\nthey're not already present and a model-specific sort is supplied.\nThe behaviour is the same as in ``apply_filters``.\n\nThis allows flexibility for clients to sort by fields on related objects\nwithout specifying all possible joins on the server beforehand.\n\nHybrid attributes\n^^^^^^^^^^^^^^^^^\n\nYou can sort by a `hybrid attribute`_: a `hybrid property`_ or a `hybrid method`_.\n\n\nPagination\n----------\n\n.. code-block:: python\n\n    from sqlalchemy_filters import apply_pagination\n\n\n    # `query` should be a SQLAlchemy query object\n\n    query, pagination = apply_pagination(query, page_number=1, page_size=10)\n\n    page_size, page_number, num_pages, total_results = pagination\n\n    assert 10 == len(query)\n    assert 10 == page_size == pagination.page_size\n    assert 1 == page_number == pagination.page_number\n    assert 3 == num_pages == pagination.num_pages\n    assert 22 == total_results == pagination.total_results\n\nFilters format\n--------------\n\nFilters must be provided in a list and will be applied sequentially.\nEach filter will be a dictionary element in that list, using the\nfollowing format:\n\n.. code-block:: python\n\n    filter_spec = [\n        {'model': 'model_name', 'field': 'field_name', 'op': '==', 'value': 'field_value'},\n        {'model': 'model_name', 'field': 'field_2_name', 'op': '!=', 'value': 'field_2_value'},\n        # ...\n    ]\n\nThe ``model`` key is optional if the original query being filtered only\napplies to one model.\n\nIf there is only one filter, the containing list may be omitted:\n\n.. code-block:: python\n\n    filter_spec = {'field': 'field_name', 'op': '==', 'value': 'field_value'}\n\nWhere ``field`` is the name of the field that will be filtered using the\noperator provided in ``op`` (optional, defaults to ``==``) and the\nprovided ``value`` (optional, depending on the operator).\n\nThis is the list of operators that can be used:\n\n- ``is_null``\n- ``is_not_null``\n- ``==``, ``eq``\n- ``!=``, ``ne``\n- ``\u003e``, ``gt``\n- ``\u003c``, ``lt``\n- ``\u003e=``, ``ge``\n- ``\u003c=``, ``le``\n- ``like``\n- ``ilike``\n- ``not_ilike``\n- ``in``\n- ``not_in``\n- ``any``\n- ``not_any``\n\nany / not_any\n^^^^^^^^^^^^^\n\nPostgreSQL specific operators allow to filter queries on columns of type ``ARRAY``.\nUse ``any`` to filter if a value is present in an array and ``not_any`` if it's not.\n\nBoolean Functions\n^^^^^^^^^^^^^^^^^\n``and``, ``or``, and ``not`` functions can be used and nested within the\nfilter specification:\n\n.. code-block:: python\n\n    filter_spec = [\n        {\n            'or': [\n                {\n                    'and': [\n                        {'field': 'field_name', 'op': '==', 'value': 'field_value'},\n                        {'field': 'field_2_name', 'op': '!=', 'value': 'field_2_value'},\n                    ]\n                },\n                {\n                    'not': [\n                        {'field': 'field_3_name', 'op': '==', 'value': 'field_3_value'}\n                    ]\n                },\n            ],\n        }\n    ]\n\n\nNote: ``or`` and ``and`` must reference a list of at least one element.\n``not`` must reference a list of exactly one element.\n\nSort format\n-----------\n\nSort elements must be provided as dictionaries in a list and will be\napplied sequentially:\n\n.. code-block:: python\n\n    sort_spec = [\n        {'model': 'Foo', 'field': 'name', 'direction': 'asc'},\n        {'model': 'Bar', 'field': 'id', 'direction': 'desc'},\n        # ...\n    ]\n\nWhere ``field`` is the name of the field that will be sorted using the\nprovided ``direction``.\n\nThe ``model`` key is optional if the original query being sorted only\napplies to one model.\n\nnullsfirst / nullslast\n^^^^^^^^^^^^^^^^^^^^^^\n\n.. code-block:: python\n\n    sort_spec = [\n        {'model': 'Baz', 'field': 'count', 'direction': 'asc', 'nullsfirst': True},\n        {'model': 'Qux', 'field': 'city', 'direction': 'desc', 'nullslast': True},\n        # ...\n    ]\n\n``nullsfirst`` is an optional attribute that will place ``NULL`` values first\nif set to ``True``, according to the `SQLAlchemy documentation \u003chttps://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.nullsfirst\u003e`__.\n\n``nullslast`` is an optional attribute that will place ``NULL`` values last\nif set to ``True``, according to the `SQLAlchemy documentation \u003chttps://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.expression.nullslast\u003e`__.\n\nIf none of them are provided, then ``NULL`` values will be sorted according\nto the RDBMS being used. SQL defines that ``NULL`` values should be placed\ntogether when sorting, but it does not specify whether they should be placed\nfirst or last.\n\nEven though both ``nullsfirst`` and ``nullslast`` are part of SQLAlchemy_,\nthey will raise an unexpected exception if the RDBMS that is being used does\nnot support them.\n\nAt the moment they are\n`supported by PostgreSQL \u003chttps://www.postgresql.org/docs/current/queries-order.html\u003e`_,\nbut they are **not** supported by SQLite and MySQL.\n\n\n\nRunning tests\n-------------\n\nThe default configuration uses **SQLite**, **MySQL** (if the driver is\ninstalled, which is the case when ``tox`` is used) and **PostgreSQL**\n(if the driver is installed, which is the case when ``tox`` is used) to\nrun the tests, with the following URIs:\n\n.. code-block:: shell\n\n    sqlite+pysqlite:///test_sqlalchemy_filters.db\n    mysql+mysqlconnector://root:@localhost:3306/test_sqlalchemy_filters\n    postgresql+psycopg2://postgres:@localhost:5432/test_sqlalchemy_filters?client_encoding=utf8'\n\nA test database will be created, used during the tests and destroyed\nafterwards for each RDBMS configured.\n\nThere are Makefile targets to run docker containers locally for both\n**MySQL** and **PostgreSQL**, using the default ports and configuration:\n\n.. code-block:: shell\n\n    $ make mysql-container\n    $ make postgres-container\n\nTo run the tests locally:\n\n.. code-block:: shell\n\n    $ # Create/activate a virtual environment\n    $ pip install tox\n    $ tox\n\nThere are some other Makefile targets that can be used to run the tests:\n\nThere are other Makefile targets to run the tests, but extra\ndependencies will have to be installed:\n\n.. code-block:: shell\n\n    $ pip install -U --editable \".[dev,mysql,postgresql]\"\n    $ # using default settings\n    $ make test\n    $ make coverage\n\n    $ # overriding DB parameters\n    $ ARGS='--mysql-test-db-uri mysql+mysqlconnector://root:@192.168.99.100:3340/test_sqlalchemy_filters' make test\n    $ ARGS='--sqlite-test-db-uri sqlite+pysqlite:///test_sqlalchemy_filters.db' make test\n\n    $ ARGS='--mysql-test-db-uri mysql+mysqlconnector://root:@192.168.99.100:3340/test_sqlalchemy_filters' make coverage\n    $ ARGS='--sqlite-test-db-uri sqlite+pysqlite:///test_sqlalchemy_filters.db' make coverage\n\n\n\nDatabase management systems\n---------------------------\n\nThe following RDBMS are supported (tested):\n\n- SQLite\n- MySQL\n- PostgreSQL\n\n\nSQLAlchemy support\n------------------\n\nThe following SQLAlchemy_ versions are supported: ``1.0``, ``1.1``,\n``1.2``, ``1.3``, ``1.4``.\n\n\nChangelog\n---------\n\nConsult the `CHANGELOG \u003chttps://github.com/juliotrigo/sqlalchemy-filters/blob/master/CHANGELOG.rst\u003e`_\ndocument for fixes and enhancements of each version.\n\n\nLicense\n-------\n\nApache 2.0. See `LICENSE \u003chttps://github.com/juliotrigo/sqlalchemy-filters/blob/master/LICENSE\u003e`_\nfor details.\n\n\n.. _SQLAlchemy: https://www.sqlalchemy.org/\n.. _hybrid attribute: https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html\n.. _hybrid property: https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html#sqlalchemy.ext.hybrid.hybrid_property\n.. _hybrid method: https://docs.sqlalchemy.org/en/13/orm/extensions/hybrid.html#sqlalchemy.ext.hybrid.hybrid_method\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuliotrigo%2Fsqlalchemy-filters","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjuliotrigo%2Fsqlalchemy-filters","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjuliotrigo%2Fsqlalchemy-filters/lists"}