Skip to content

feat: Add DynamoDB backend#71

Open
smitrob wants to merge 11 commits into
gorilla-co:masterfrom
smitrob:feature/dynamodb-backend
Open

feat: Add DynamoDB backend#71
smitrob wants to merge 11 commits into
gorilla-co:masterfrom
smitrob:feature/dynamodb-backend

Conversation

@smitrob
Copy link
Copy Markdown

@smitrob smitrob commented Apr 16, 2026

Summary

Adds a new odata_query.dynamo backend that converts OData $filter expressions directly to boto3 ConditionBase objects — no eval() required.

This is an additive-only change. No existing files are modified.


Why

DynamoDB is a widely-used AWS database with no current OData backend in this library. The Django and SQLAlchemy backends produce SQL strings, but DynamoDB uses a completely different expression model (boto3 condition objects). A dedicated visitor closes that gap cleanly.


What's Added

odata_query/dynamo/ (new backend module)

  • base.pyAstToDynamoConditionVisitor — walks the OData AST and returns live boto3 ConditionBase objects at every node
  • __init__.py — exports AstToDynamoConditionVisitor and apply_odata_query

Supported OData operations:

Category Operations
Comparison eq, ne, lt, le, gt, ge
Logical and, or, not
Membership in, between
String functions contains, startswith
Attribute checks exists, not_exists
Null handling eq nullnot_exists OR attribute_type('NULL'); ne nullexists AND NOT attribute_type('NULL')

Unsupported functions (e.g. endswith) raise UnsupportedFunctionException immediately — no silent fallback.

tests/test_dynamodb_backend.py

20 tests covering all operators, boolean logic, null semantics, nested paths, and error cases.


Usage

from odata_query.dynamo import apply_odata_query

condition = apply_odata_query("status eq 'active' and age gt 18")
# Returns: Attr('status').eq('active') & Attr('age').gt(18)

response = table.query(
    KeyConditionExpression=Key('pk').eq('TENANT#acme'),
    FilterExpression=condition,
)

Backward Compatibility

  • ✅ No changes to ast.py, grammar.py, visitor.py, or any existing backend
  • ✅ No changes to existing tests
  • boto3 is an optional dependency — doesn't affect core package installs
  • ✅ Fully additive — all existing imports and behavior unchanged

Dependencies

boto3 should be added as an optional extra in pyproject.toml:

boto3 = { version = ">=1.26", optional = true }

[tool.poetry.extras]
dynamodb = ["boto3"]

I've left pyproject.toml unchanged in this PR so maintainers can apply the version pin they prefer.

Robert Smith and others added 11 commits December 15, 2023 12:40
Adds a new `odata_query.dynamo` backend that converts OData $filter
expressions directly to boto3 ConditionBase objects (no eval()).

The visitor (`AstToDynamoConditionVisitor`) handles:
- Comparison: eq, ne, lt, le, gt, ge
- Logical: and, or, not
- Membership: in, between
- String functions: contains, startswith
- Special: exists, not_exists
- Null handling: maps `eq null` / `ne null` to DynamoDB
  attribute_not_exists / attribute_type semantics

boto3 is an optional dependency (install with `odata-query[dynamodb]`).

163 tests, all passing.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant