A lightweight, simple and fast parser for OData V4 query options supporting standard query parameters. Provides helper functions to apply OData V4 query options to ORM/ODM queries such as SQLAlchemy, PyMongo and Beanie.
Project description
OData V4 Query
A lightweight, simple and fast parser for OData V4 query options supporting standard query parameters. Provides helper functions to apply OData V4 query options to ORM/ODM queries such as SQLAlchemy, PyMongo and Beanie.
Table of Contents
Features
-
Support for the following OData V4 standard query parameters:
$count- Include count of items$expand- Expand related entities$filter- Filter results$format- Response format (json, xml, csv, tsv)$orderby- Sort results$search- Search items$select- Select specific fields$skip- Skip N items$top- Limit to N items$page- Page number
-
Comprehensive filter expression support:
- Comparison operators:
eq,ne,gt,ge,lt,le,in,nin - Logical operators:
and,or,not,nor - Collection operators:
has - String functions:
startswith,endswith,contains,substring,tolower,toupper - Nested field filtering:
user/name,profile/address/city
- Comparison operators:
-
Utility functions to apply options to ORM/ODM queries.
- See utility functions for more information.
Requirements
Python 3.10+beanie 1.23+ (optional, for Beanie ODM utils)pymongo 4.3+ (optional, for PyMongo utils)sqlalchemy 2.0+ (optional, for SQLAlchemy utils)
Installation
You can simply install odata-v4-query from PyPI:
pip install odata-v4-query
To install all the optional dependencies to use all the ORM/ODM utils:
pip install odata-v4-query[all]
You can also install the dependencies for a specific ORM/ODM util:
pip install odata-v4-query[beanie]
pip install odata-v4-query[pymongo]
pip install odata-v4-query[sqlalchemy]
Quick Start
from odata_v4_query import ODataQueryParser, ODataFilterParser
# Create parser instance
parser = ODataQueryParser()
# Parse a complete URL
options = parser.parse_url('https://example.com/odata?$count=true&$top=10&$skip=20')
# Parse just the query string
options = parser.parse_query_string("$filter=name eq 'John' and age gt 25")
# Parse filter expressions
filter_parser = ODataFilterParser()
ast = filter_parser.parse("name eq 'John' and age gt 25")
# Evaluate filter expressions
filter_parser.evaluate(ast)
# Filter with nested fields
options = parser.parse_query_string("$filter=user/name eq 'Alice'")
options = parser.parse_query_string("$filter=profile/address/city eq 'Chicago'")
Utility Functions
You to need to install the required dependencies for the ORM/ODM you want to use.
[!NOTE] If the
$pageoption is used, it is converted to$skipand$top. If$topis not provided, it defaults to 100. The$skipis computed as(page - 1) * top. If$skipis provided, it is overwritten.
Beanie
Use the apply_to_beanie_query() function to apply options to a Beanie query.
from beanie import Document
from odata_v4_query import ODataQueryParser
from odata_v4_query.utils.beanie import apply_to_beanie_query
class User(Document):
name: str
email: str
age: int
# Create parser instance
parser = ODataQuery_parser()
# Parse a complete URL
options = parser.parse_query_string("$top=10&$skip=20&$filter=name eq 'John'")
# Apply options to a new query
query = apply_to_beanie_query(options, User)
# Apply options to an existing query
query = User.find()
query = apply_to_beanie_query(options, query)
Nested field filtering is supported using the / separator for accessing nested
document fields. Both single-level and multi-level nesting are supported:
# Single-level: Filter by nested field
options = parser.parse_query_string("$filter=profile/city eq 'Chicago'")
query = apply_to_beanie_query(options, User)
# Multi-level: Filter by deeply nested field
options = parser.parse_query_string("$filter=profile/address/city eq 'Chicago'")
query = apply_to_beanie_query(options, User)
# Use with string functions
options = parser.parse_query_string("$filter=startswith(profile/city, 'Chi')")
query = apply_to_beanie_query(options, User)
The $search option is only supported if search_fields is provided.
options = parser.parse_query_string('$search=John')
# Search "John" in "name" and "email" fields
query = apply_to_beanie_query(options, User, search_fields=['name', 'email'])
The $select option is only supported if parse_select is True.
If projection_model is provided, the results are projected with a Pydantic
model, otherwise a dictionary.
from pydantic import BaseModel
class UserProjection(BaseModel):
name: str
email: str
options = parser.parse_query_string("$select=name,email")
# Project as a dictionary (default)
query = apply_to_beanie_query(options, User, parse_select=True)
# Project using a Pydantic model
query = apply_to_beanie_query(
options, User, parse_select=True, projection_model=UserProjection
)
[!NOTE] The
$expandand$formatoptions won't be applied. You may need to handle them manually. Also, thesubstring,tolowerandtoupperfunctions are not supported.
PyMongo
Use the get_query_from_options() function to get a MongoDB query from options
to be applied to a PyMongo query.
from pymongo import MongoClient, ASCENDING, DESCENDING
from odata_v4_query import ODataQueryParser
from odata_v4_query.utils.pymongo import PyMongoQuery, get_query_from_options
client = MongoClient()
db = client['db']
# Create parser instance
parser = ODataQuery_parser()
# Parse a complete URL
options = parser.parse_query_string("$top=10&$skip=20&$filter=name eq 'John'")
# Get a PyMongo query from options
query = get_query_from_options(options)
# Apply query to collection
db.users.find(**query)
# Using keyword arguments
db.users.find(
skip=query.skip,
limit=query.limit,
filter=query.filter,
sort=query.sort,
projection=query.projection,
)
Nested field filtering is supported using the / separator for accessing nested
document fields. Both single-level and multi-level nesting are supported:
# Single-level: Filter by nested field
options = parser.parse_query_string("$filter=profile/city eq 'Chicago'")
query = get_query_from_options(options)
# Multi-level: Filter by deeply nested field
options = parser.parse_query_string("$filter=profile/address/city eq 'Chicago'")
query = get_query_from_options(options)
# Use with string functions
options = parser.parse_query_string("$filter=contains(profile/city, 'ago')")
query = get_query_from_options(options)
The $search option is only supported if search_fields is provided.
It overrides the $filter option.
options = parser.parse_query_string('$search=John')
# Search "John" in "name" and "email" fields
query = get_query_from_options(options, search_fields=['name', 'email'])
The $select option is only supported if parse_select is True.
options = parser.parse_query_string("$select=name,email")
# Parse $select option
query = get_query_from_options(options, parse_select=True)
[!NOTE] The
$count,$expandand$formatoptions won't be applied. You may need to handle them manually. Also, thesubstring,tolowerandtoupperfunctions are not supported.
SQLAlchemy
Use the apply_to_sqlalchemy_query() function to apply options to a SQLAlchemy
query.
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from odata_v4_query import ODataQueryParser
from odata_v4_query.utils.sqlalchemy import apply_to_sqlalchemy_query
class User(DeclarativeBase):
name: Mapped[str] = mapped_column()
email: Mapped[str] = mapped_column()
age: Mapped[int] = mapped_column()
# Create parser instance
parser = ODataQuery_parser()
# Parse a complete URL
options = parser.parse_query_string("$top=10&$skip=20&$filter=name eq 'John'")
# Apply options to a new query
query = apply_to_sqlalchemy_query(options, User)
# Apply options to an existing query
query = select(User)
query = apply_to_sqlalchemy_query(options, query)
Nested field filtering is supported using the / separator for filtering on
related entities. Both single-level and multi-level nesting are supported:
# Single-level: Filter by related entity field
options = parser.parse_query_string("$filter=user/name eq 'Alice'")
query = apply_to_sqlalchemy_query(options, Post)
# Multi-level: Filter by deeply nested field
options = parser.parse_query_string("$filter=user/profile/address/city eq 'Chicago'")
query = apply_to_sqlalchemy_query(options, Post)
# Use with string functions
options = parser.parse_query_string("$filter=tolower(user/name) eq 'alice'")
query = apply_to_sqlalchemy_query(options, Post)
# Multi-level with functions
options = parser.parse_query_string("$filter=startswith(user/profile/address/city, 'Chi')")
query = apply_to_sqlalchemy_query(options, Post)
# Combine with other filters
options = parser.parse_query_string("$filter=user/name eq 'Alice' and rating gt 3")
query = apply_to_sqlalchemy_query(options, Post)
The $search option is only supported if search_fields is provided.
options = parser.parse_query_string('$search=John')
# Search "John" in "name" and "email" fields
query = apply_to_sqlalchemy_query(
options, User, search_fields=['name', 'email']
)
The $expand option performs a
joined eager loading
using left outer join.
options = parser.parse_query_string('$expand=posts')
# Perform joined eager loading on "posts"
query = apply_to_sqlalchemy_query(options, User)
[!NOTE] The
$formatoption won't be applied. You may need to handle it manually. Also, thehasandnoroperators are not supported in SQL, so they are converted to aLIKEandNOTexpressions, respectively.
Contributing
See the contribution guidelines.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Support
If you find this project useful, give it a ⭐ on GitHub!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file odata_v4_query-0.4.1.tar.gz.
File metadata
- Download URL: odata_v4_query-0.4.1.tar.gz
- Upload date:
- Size: 34.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
91af87560a36301f6429a9247256db6b10d431273f0e30e0213bd0b4748c6f21
|
|
| MD5 |
2d0f2d387d0a7f318bf61b42aebb54e8
|
|
| BLAKE2b-256 |
37222ecc699bf6baa327ca25a3cfd93d91083015102eea83001e2953da38acf6
|
Provenance
The following attestation bundles were made for odata_v4_query-0.4.1.tar.gz:
Publisher:
publish.yml on daireto/odata-v4-query
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odata_v4_query-0.4.1.tar.gz -
Subject digest:
91af87560a36301f6429a9247256db6b10d431273f0e30e0213bd0b4748c6f21 - Sigstore transparency entry: 702295147
- Sigstore integration time:
-
Permalink:
daireto/odata-v4-query@88239a687af15c5d2f673584cdd3129e88c72d1d -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/daireto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88239a687af15c5d2f673584cdd3129e88c72d1d -
Trigger Event:
push
-
Statement type:
File details
Details for the file odata_v4_query-0.4.1-py3-none-any.whl.
File metadata
- Download URL: odata_v4_query-0.4.1-py3-none-any.whl
- Upload date:
- Size: 31.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
883f02914e8b4b69cc94cb770e8b23b55fe2d8213170e59326c8609cf9358879
|
|
| MD5 |
284a5aae9da501ba019a4c534010ee7e
|
|
| BLAKE2b-256 |
8bdcad54e983557c3027b1995b2d70bd019b0c4dfa7bc77561657fc350d4d58e
|
Provenance
The following attestation bundles were made for odata_v4_query-0.4.1-py3-none-any.whl:
Publisher:
publish.yml on daireto/odata-v4-query
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
odata_v4_query-0.4.1-py3-none-any.whl -
Subject digest:
883f02914e8b4b69cc94cb770e8b23b55fe2d8213170e59326c8609cf9358879 - Sigstore transparency entry: 702295151
- Sigstore integration time:
-
Permalink:
daireto/odata-v4-query@88239a687af15c5d2f673584cdd3129e88c72d1d -
Branch / Tag:
refs/tags/v0.4.1 - Owner: https://github.com/daireto
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88239a687af15c5d2f673584cdd3129e88c72d1d -
Trigger Event:
push
-
Statement type: