Skip to main content

OData V4 Query

Supported Python versions Package version Publish Coverage Status License

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
  • Utility functions to apply options to ORM/ODM queries.

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.

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
)

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)

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)

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!

Metadata

Release files for odata-v4-query 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for odata-v4-query 0.4.1
File Size Uploaded Provenance Actions
odata_v4_query-0.4.1.tar.gz 34.7 kB With provenance Details for odata_v4_query-0.4.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for odata-v4-query 0.4.1
File Interpreter ABI Platform Actions
odata_v4_query-0.4.1-py3-none-any.whl Python 3 none any Details for odata_v4_query-0.4.1-py3-none-any.whl

Total release size: 65.7 kB

Release files / odata_v4_query-0.4.1.tar.gz

Download URL odata_v4_query-0.4.1.tar.gz
Size 34.7 kB
Tags Source
SHA-256 checksum
How to use checksums
91af87560a36301f6429a9247256db6b10d431273f0e30e0213bd0b4748c6f21
BLAKE2b-256 checksum
How to use checksums
37222ecc699bf6baa327ca25a3cfd93d91083015102eea83001e2953da38acf6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 16, 2025.

Transparency log

Release files / odata_v4_query-0.4.1-py3-none-any.whl

Download URL odata_v4_query-0.4.1-py3-none-any.whl
Size 31.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
883f02914e8b4b69cc94cb770e8b23b55fe2d8213170e59326c8609cf9358879
BLAKE2b-256 checksum
How to use checksums
8bdcad54e983557c3027b1995b2d70bd019b0c4dfa7bc77561657fc350d4d58e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 16, 2025.

Transparency log

Security for odata-v4-query 0.4.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for odata_v4_query-0.4.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 16, 2025.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page