Metadata-Version: 2.4
Name: mongopyengine
Version: 1.0.0
Summary: Fluent query-builder helpers for MongoEngine fields
Author-email: GhosT <cd.tgr9@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/azayn-labs/mongopyengine
Project-URL: Repository, https://github.com/azayn-labs/mongopyengine
Project-URL: Issues, https://github.com/azayn-labs/mongopyengine/issues
Keywords: mongoengine,mongodb,odm,query-builder,query
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mongoengine
Dynamic: license-file

# mongopyengine

`mongopyengine` is a small query-builder layer for [MongoEngine](https://mongoengine-odm.readthedocs.io/).
It wraps MongoEngine fields with fluent helper objects that generate query dictionaries and sort keys without changing how you define your documents.

The library is intentionally small:

- keep using normal MongoEngine `Document` models
- build query fragments with chained helper methods
- pass the resulting dictionary straight into `QuerySet` operations

## What It Does

`mongopyengine` adds an abstract document base class, `MongoPyEngineDocument`, with two class helpers:

- `FieldQuery(field)` returns a wrapper object for a MongoEngine field
- `OrderBy(field, ascending=True)` returns a MongoEngine-compatible sort key

The wrapper returned by `FieldQuery(...)` depends on the field type:

- geo fields -> `GeoField`
- list fields -> `ListField`
- everything else -> `StringField`

`StringField` inherits the base comparison operators, so scalar fields such as strings, integers, and similar types can still use methods like `Eq`, `Gt`, or `Exists`.

## Requirements

- Python 3.10+
- `mongoengine`

Project metadata lives in [pyproject.toml](pyproject.toml).

## Installation

Install from this repository:

```bash
pip install .
```

With `uv`:

```bash
uv pip install .
```

## Quick Start

Define your documents with regular MongoEngine fields and inherit from `MongoPyEngineDocument`.

```python
from mongoengine import IntField
from mongoengine import ListField as MongoListField
from mongoengine import PointField
from mongoengine import StringField as MongoStringField

from mongopyengine import MongoPyEngineDocument


class User(MongoPyEngineDocument):
    name = MongoStringField(required=True)
    age = IntField()
    tags = MongoListField(MongoStringField())
    home = PointField()
```

Build a query dictionary:

```python
query = User.FieldQuery(User.name).IContains('ada').GetQuery()

# {'name__icontains': 'ada'}
results = User.objects(**query)
```

Build a numeric filter:

```python
query = User.FieldQuery(User.age).Gte(18).GetQuery()

# {'age__gte': 18}
results = User.objects(**query)
```

Build a list filter:

```python
query = User.FieldQuery(User.tags).At(0, 'admin').GetQuery()

# {'tags__0': 'admin'}
results = User.objects(**query)
```

Build a geo filter:

```python
query = User.FieldQuery(User.home).GeoNear(
    (-73.9857, 40.7484),
    point_type='Point',
    max_distance=500,
).GetQuery()

results = User.objects(**query)
```

Sort results:

```python
sort_key = User.OrderBy(User.name, ascending=False)
results = User.objects.order_by(sort_key)
```

## Public API

Top-level imports are re-exported in [mongopyengine/__init__.py](mongopyengine/__init__.py):

```python
from mongopyengine import GeoField, ListField, StringField, MongoPyEngineDocument
```

### `MongoPyEngineDocument`

Implemented in [mongopyengine/main.py](mongopyengine/main.py).

#### `FieldQuery(field)`

Returns one of the following wrappers:

- `GeoField` for `GeoPointField` and `GeoJsonBaseField`
- `ListField` for MongoEngine `ListField`
- `StringField` for all other fields

Examples:

```python
User.FieldQuery(User.name)   # StringField
User.FieldQuery(User.age)    # StringField
User.FieldQuery(User.tags)   # ListField
User.FieldQuery(User.home)   # GeoField
```

#### `OrderBy(field, ascending=True)`

Returns a sort key string that can be passed to MongoEngine `order_by`:

```python
User.OrderBy(User.name)                    # 'name'
User.OrderBy(User.name, ascending=False)   # '-name'
```

## Query Builder Classes

### Base Operators

Implemented in [mongopyengine/field.py](mongopyengine/field.py).

These methods are available on `BaseField`, and therefore on `StringField`, `ListField`, and `GeoField` as well.

| Method | Output example |
| --- | --- |
| `Eq(value)` | `{'field': value}` |
| `Ne(value)` | `{'field__ne': value}` |
| `Lt(value)` | `{'field__lt': value}` |
| `Lte(value)` | `{'field__lte': value}` |
| `Gt(value)` | `{'field__gt': value}` |
| `Gte(value)` | `{'field__gte': value}` |
| `Not(value=None)` | `{'field__not': value}` |
| `In(values)` | `{'field__in': values}` |
| `Nin(values)` | `{'field__nin': values}` |
| `Mod(divisor, remainder)` | `{'field__mod': (divisor, remainder)}` |
| `All(values)` | `{'field__all': values}` |
| `Size(value)` | `{'field__size': value}` |
| `Exists(value)` | `{'field__exists': value}` |
| `Raw(value)` | `{'__raw__': value}` |
| `GetQuery()` | returns the built query dictionary |

Example:

```python
query = User.FieldQuery(User.age).Gt(21).GetQuery()

# {'age__gt': 21}
```

### `StringField`

Implemented in [mongopyengine/string.py](mongopyengine/string.py).

Adds string-oriented operators on top of the base operators.

| Method | Output example |
| --- | --- |
| `Exact(value)` | `{'name__exact': value}` |
| `IExact(value)` | `{'name__iexact': value}` |
| `Contains(value)` | `{'name__contains': value}` |
| `IContains(value)` | `{'name__icontains': value}` |
| `StartsWith(value)` | `{'name__startswith': value}` |
| `IStartsWith(value)` | `{'name__istartswith': value}` |
| `EndsWith(value)` | `{'name__endswith': value}` |
| `IEndsWith(value)` | `{'name__iendswith': value}` |
| `WholeWord(value)` | `{'name__wholeword': value}` |
| `IWholteWord(value)` | `{'name__iwholeword': value}` |
| `Regex(value)` | `{'name__regex': value}` |
| `IRegex(value)` | `{'name__iregex': value}` |
| `Match(value)` | `{'name__match': value}` |

Example:

```python
query = User.FieldQuery(User.name).StartsWith('Ad').GetQuery()

# {'name__startswith': 'Ad'}
```

Note: the method name `IWholteWord` is spelled exactly as implemented in the current codebase.

### `ListField`

Implemented in [mongopyengine/list.py](mongopyengine/list.py).

Adds list-specific helpers.

| Method | Output example |
| --- | --- |
| `At(index, value)` | `{'tags__2': value}` |
| `Exact(value)` | `{'tags__exact': value}` |

Example:

```python
query = User.FieldQuery(User.tags).Exact(['admin', 'editor']).GetQuery()

# {'tags__exact': ['admin', 'editor']}
```

### `GeoField`

Implemented in [mongopyengine/geo.py](mongopyengine/geo.py).

Supports common MongoEngine geospatial query operators.

| Method | Output example |
| --- | --- |
| `GeoWithin(coordinates)` | `{'field__geo_within': coordinates}` |
| `GeoWithin(coordinates, coordinate_type='Polygon')` | `{'field__geo_within': {'type': 'Polygon', 'coordinates': coordinates}}` |
| `GeoWithinBox(left, right)` | `{'field__geo_within_box': [left, right]}` |
| `GeoWithinPolygon(points)` | `{'field__geo_within_polygon': points}` |
| `GeoWithinCenter(center, radius)` | `{'field__geo_within_center': [center, radius]}` |
| `GeoIntersects(coordinates)` | `{'field__geo_intersects': coordinates}` |
| `GeoIntersects(coordinates, coordinate_type='LineString')` | `{'field__geo_intersects': {'type': coordinate_type, 'coordinates': coordinates}}` |
| `GeoNear(point)` | `{'field__geo_near': point}` |
| `GeoNear(point, point_type='Point')` | `{'field__geo_near': {'type': 'Point', 'coordinates': point}}` |

`GeoNear(...)` may also add extra distance constraints to the same query:

- `max_distance` -> `field__max_distance`
- `min_distance` -> `field__min_distance`

Example:

```python
query = User.FieldQuery(User.home).GeoNear(
    (-73.9857, 40.7484),
    point_type='Point',
    max_distance=1000,
    min_distance=50,
).GetQuery()

# {
#   'home__geo_near': {'type': 'Point', 'coordinates': (-73.9857, 40.7484)},
#   'home__max_distance': 1000,
#   'home__min_distance': 50,
# }
```

## Behavior Notes

### Query builders are mutable

Builder instances store state internally and are intended to describe one query expression at a time.

Preferred usage:

```python
name_query = User.FieldQuery(User.name).IContains('ada').GetQuery()
age_query = User.FieldQuery(User.age).Gte(18).GetQuery()
```

Avoid reusing the same builder object for unrelated query expressions.

### `FieldQuery` is type-light for non-list, non-geo fields

`FieldQuery(...)` returns `StringField` for all scalar fields, not only textual ones. This means integer and similar fields still work for base operators such as `Eq`, `Gt`, and `Exists`, because `StringField` inherits from `BaseField`.

Example:

```python
User.FieldQuery(User.age).Gte(30).GetQuery()
```

### The library builds query fragments; it does not execute them

`mongopyengine` only constructs query dictionaries and sort keys. Execution still happens through normal MongoEngine APIs such as `objects`, `filter`, and `order_by`.

## License

This project is licensed under the Apache License 2.0. See [LICENSE](LICENSE).
