mongoforge-odm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. mongoforge_odm-0.1.0/PKG-INFO +232 -0
  2. mongoforge_odm-0.1.0/README.md +204 -0
  3. mongoforge_odm-0.1.0/mongoforge/__init__.py +5 -0
  4. mongoforge_odm-0.1.0/mongoforge/client.py +19 -0
  5. mongoforge_odm-0.1.0/mongoforge/exceptions.py +59 -0
  6. mongoforge_odm-0.1.0/mongoforge/fields/__init__.py +24 -0
  7. mongoforge_odm-0.1.0/mongoforge/fields/base.py +63 -0
  8. mongoforge_odm-0.1.0/mongoforge/fields/types.py +149 -0
  9. mongoforge_odm-0.1.0/mongoforge/fields/utils.py +0 -0
  10. mongoforge_odm-0.1.0/mongoforge/managers/__init__.py +5 -0
  11. mongoforge_odm-0.1.0/mongoforge/managers/base.py +16 -0
  12. mongoforge_odm-0.1.0/mongoforge/managers/create.py +25 -0
  13. mongoforge_odm-0.1.0/mongoforge/managers/delete.py +25 -0
  14. mongoforge_odm-0.1.0/mongoforge/managers/helpers.py +20 -0
  15. mongoforge_odm-0.1.0/mongoforge/managers/read.py +73 -0
  16. mongoforge_odm-0.1.0/mongoforge/managers/update.py +56 -0
  17. mongoforge_odm-0.1.0/mongoforge/meta.py +114 -0
  18. mongoforge_odm-0.1.0/mongoforge/models.py +132 -0
  19. mongoforge_odm-0.1.0/mongoforge/settings.py +7 -0
  20. mongoforge_odm-0.1.0/mongoforge/utils/__init__.py +8 -0
  21. mongoforge_odm-0.1.0/mongoforge/utils/collections.py +0 -0
  22. mongoforge_odm-0.1.0/mongoforge/utils/dates.py +4 -0
  23. mongoforge_odm-0.1.0/mongoforge/utils/validation.py +4 -0
  24. mongoforge_odm-0.1.0/mongoforge_odm.egg-info/PKG-INFO +232 -0
  25. mongoforge_odm-0.1.0/mongoforge_odm.egg-info/SOURCES.txt +28 -0
  26. mongoforge_odm-0.1.0/mongoforge_odm.egg-info/dependency_links.txt +1 -0
  27. mongoforge_odm-0.1.0/mongoforge_odm.egg-info/requires.txt +7 -0
  28. mongoforge_odm-0.1.0/mongoforge_odm.egg-info/top_level.txt +1 -0
  29. mongoforge_odm-0.1.0/pyproject.toml +46 -0
  30. mongoforge_odm-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,232 @@
1
+ Metadata-Version: 2.4
2
+ Name: mongoforge-odm
3
+ Version: 0.1.0
4
+ Summary: A Django-inspired MongoDB ODM for Python
5
+ Author: Saurabh Singh
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Sourabh7singh/MongoForge
8
+ Project-URL: Repository, https://github.com/Sourabh7singh/MongoForge
9
+ Project-URL: Issues, https://github.com/Sourabh7singh/MongoForge/issues
10
+ Keywords: mongodb,odm,orm,pymongo,django,schema,validation
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Database
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: pymongo>=4.6
23
+ Requires-Dist: python-dotenv>=1.0
24
+ Requires-Dist: python-slugify>=8.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Requires-Dist: mongomock>=4.0; extra == "dev"
28
+
29
+ # 🔧 MongoForge
30
+
31
+ A **Django-inspired MongoDB ODM** for Python — define document schemas as classes with typed fields, validate data automatically, and perform CRUD operations through a clean manager API.
32
+
33
+ Built on [PyMongo](https://pymongo.readthedocs.io/).
34
+
35
+ ---
36
+
37
+ ## ✨ Features
38
+
39
+ - **Declarative schemas** — define documents as Python classes with typed fields
40
+ - **10 built-in field types** — `StringField`, `IntField`, `FloatField`, `BoolField`, `ListField`, `DateTimeField`, `DictField`, `ObjectIdField`, `EmbeddedField` + extensible `BaseField`
41
+ - **Rich validation** — required, min/max length, min/max value, regex, choices, custom validators
42
+ - **Metaclass magic** — automatic field collection, inheritance, collection naming
43
+ - **Manager API** — `create`, `bulk_create`, `all`, `first`, `get`, `filter`, `count`, `update`, `update_one`, `update_many`, `delete_one`, `delete_many`
44
+ - **Django-style filter operators** — `age__gte=18`, `name__in=[...]`, `status__ne="inactive"` (supports: `gt`, `gte`, `lt`, `lte`, `ne`, `in`, `nin`)
45
+ - **Auto timestamps** — `DateTimeField(auto_now=True)` and `auto_now_add=True`
46
+ - **Attribute access** — `user.name` instead of `user["name"]`
47
+ - **Serialization** — `to_mongo()` and `from_mongo()` for round-trip conversion
48
+ - **Abstract models & inheritance** — share fields across model hierarchies
49
+
50
+ ---
51
+
52
+ ## 📦 Installation
53
+
54
+ ```bash
55
+ # Clone the repository
56
+ git clone https://github.com/yourusername/MongoForge.git
57
+ cd MongoForge
58
+
59
+ # Create and activate a virtual environment
60
+ python -m venv env
61
+ env\Scripts\activate # Windows
62
+ # source env/bin/activate # macOS/Linux
63
+
64
+ # Install dependencies
65
+ pip install pymongo python-dotenv python-slugify
66
+
67
+ # For running tests (no live MongoDB needed)
68
+ pip install pytest mongomock
69
+ ```
70
+
71
+ ### Environment Setup
72
+
73
+ Create a `.env` file in the project root:
74
+
75
+ ```env
76
+ MONGO_URI=mongodb://localhost:27017/
77
+ DATABASE_NAME=mongoforge
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 🚀 Quick Start
83
+
84
+ ### Define a Model
85
+
86
+ ```python
87
+ from mongoforge import MongoForge
88
+ from mongoforge.fields import StringField, IntField, BoolField, DateTimeField, ListField
89
+
90
+ class User(MongoForge):
91
+ name = StringField(required=True, min_length=2, max_length=100)
92
+ email = StringField(required=True, regex=r"^[\w.+-]+@[\w-]+\.[\w.-]+$")
93
+ age = IntField(min_value=0, max_value=150)
94
+ is_admin = BoolField(default=False)
95
+ tags = ListField(StringField())
96
+ created = DateTimeField(auto_now_add=True)
97
+ updated = DateTimeField(auto_now=True)
98
+
99
+ class Meta:
100
+ collection = "users"
101
+ ```
102
+
103
+ ### Create & Validate
104
+
105
+ ```python
106
+ user = User(name="Saurabh", email="saurabh@example.com", age=25)
107
+
108
+ # Validate before saving
109
+ user.validate() # raises ValidationError if invalid
110
+
111
+ # Serialize to MongoDB document
112
+ doc = user.to_mongo()
113
+
114
+ # Insert into database
115
+ User.objects.create(doc)
116
+ ```
117
+
118
+ ### Query
119
+
120
+ ```python
121
+ # Get all documents
122
+ User.objects.all() # → list[dict]
123
+
124
+ # Filter with operators
125
+ User.objects.filter(age__gte=18) # age >= 18
126
+ User.objects.filter(name__in=["Alice", "Bob"])
127
+
128
+ # Get single document
129
+ User.objects.get({"email": "saurabh@example.com"})
130
+
131
+ # Count
132
+ User.objects.count()
133
+ ```
134
+
135
+ ### Update & Delete
136
+
137
+ ```python
138
+ # Update by ID
139
+ User.objects.update(id="...", update_data={"name": "Updated Name"})
140
+
141
+ # Update many
142
+ User.objects.update_many({"age": {"$gte": 30}}, {"is_admin": True})
143
+
144
+ # Delete
145
+ User.objects.delete_one({"name": "Alice"})
146
+ User.objects.delete_many({"age": {"$lt": 18}})
147
+ ```
148
+
149
+ ### Validation in Action
150
+
151
+ ```python
152
+ from mongoforge.exceptions import ValidationError
153
+
154
+ user = User(name="A", email="not-an-email", age=-5)
155
+ try:
156
+ user.validate()
157
+ except ValidationError as exc:
158
+ print(exc)
159
+ # Validation failed. name: Minimum length is 2; email: Value does not match regex ...; age: Minimum value is 0
160
+ print(exc.errors)
161
+ # {'name': ['Minimum length is 2'], 'email': ['Value does not match regex ...'], 'age': ['Minimum value is 0']}
162
+ ```
163
+
164
+ ---
165
+
166
+ ## 📋 Field Types
167
+
168
+ | Field | Python Type | Extra Options |
169
+ |---|---|---|
170
+ | `StringField` | `str` | `min_length`, `max_length`, `regex` |
171
+ | `IntField` | `int` | `min_value`, `max_value` |
172
+ | `FloatField` | `float` | — |
173
+ | `BoolField` | `bool` | — |
174
+ | `ListField` | `list` | `field` (inner field for item validation) |
175
+ | `DateTimeField` | `datetime` | `auto_now`, `auto_now_add` |
176
+ | `DictField` | `dict` | — |
177
+ | `ObjectIdField` | `ObjectId` | Auto-generates if `None` |
178
+ | `EmbeddedField` | `dict` | `document_class` (stub — Phase 4) |
179
+
180
+ **Common options** (all fields): `required`, `default`, `unique`, `choices`, `validators`, `null`
181
+
182
+ ---
183
+
184
+ ## 🏗️ Architecture
185
+
186
+ ```
187
+ Model Class → MongoForgeMeta (metaclass) → collects fields + attaches MongoManager
188
+
189
+ ┌─────────────────────────────────────┘
190
+
191
+ MongoManager (cls.objects)
192
+ ├── CreateMixin → create(), bulk_create()
193
+ ├── ReadMixin → all(), first(), get(), filter(), count()
194
+ ├── UpdateMixin → update(), update_one(), update_many()
195
+ └── DeleteMixin → delete_one(), delete_many()
196
+ ```
197
+
198
+ ---
199
+
200
+ ## 🧪 Running Tests
201
+
202
+ Tests use [mongomock](https://github.com/mongomock/mongomock) — **no live MongoDB required**.
203
+
204
+ ```bash
205
+ # With venv activated
206
+ python -m pytest mongoforge/tests/ -v
207
+
208
+ # Or using venv python directly
209
+ env\Scripts\python.exe -m pytest mongoforge/tests/ -v
210
+ ```
211
+
212
+ **148 tests** covering:
213
+ - All 10 field types + validation paths
214
+ - Model instances, attribute access, serialization
215
+ - Metaclass, inheritance, abstract models
216
+ - Full CRUD operations + filter operators
217
+ - Exception hierarchy + formatting
218
+
219
+ ---
220
+
221
+ ## 🗺️ Roadmap
222
+
223
+ | Phase | Description | Status |
224
+ |---|---|---|
225
+ | **Phase 1** | Fields & Schema | ✅ ~95% |
226
+ | **Phase 2** | QuerySet (chainable queries) | ⬜ Not started |
227
+ | **Phase 3** | Model Lifecycle (`save()`, `delete()`, `reload()`, dirty tracking) | 🔶 ~50% |
228
+ | **Phase 4** | Relationships (EmbeddedDocument, ReferenceField) | ⬜ Not started |
229
+ | **Phase 5** | Indexes, Middleware & CLI | ⬜ Not started |
230
+ | **Phase 6** | Packaging & Developer Experience | 🔶 ~30% |
231
+
232
+ See [implementation_plan.md](implementation_plan.md) for the full roadmap with target APIs.
@@ -0,0 +1,204 @@
1
+ # 🔧 MongoForge
2
+
3
+ A **Django-inspired MongoDB ODM** for Python — define document schemas as classes with typed fields, validate data automatically, and perform CRUD operations through a clean manager API.
4
+
5
+ Built on [PyMongo](https://pymongo.readthedocs.io/).
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ - **Declarative schemas** — define documents as Python classes with typed fields
12
+ - **10 built-in field types** — `StringField`, `IntField`, `FloatField`, `BoolField`, `ListField`, `DateTimeField`, `DictField`, `ObjectIdField`, `EmbeddedField` + extensible `BaseField`
13
+ - **Rich validation** — required, min/max length, min/max value, regex, choices, custom validators
14
+ - **Metaclass magic** — automatic field collection, inheritance, collection naming
15
+ - **Manager API** — `create`, `bulk_create`, `all`, `first`, `get`, `filter`, `count`, `update`, `update_one`, `update_many`, `delete_one`, `delete_many`
16
+ - **Django-style filter operators** — `age__gte=18`, `name__in=[...]`, `status__ne="inactive"` (supports: `gt`, `gte`, `lt`, `lte`, `ne`, `in`, `nin`)
17
+ - **Auto timestamps** — `DateTimeField(auto_now=True)` and `auto_now_add=True`
18
+ - **Attribute access** — `user.name` instead of `user["name"]`
19
+ - **Serialization** — `to_mongo()` and `from_mongo()` for round-trip conversion
20
+ - **Abstract models & inheritance** — share fields across model hierarchies
21
+
22
+ ---
23
+
24
+ ## 📦 Installation
25
+
26
+ ```bash
27
+ # Clone the repository
28
+ git clone https://github.com/yourusername/MongoForge.git
29
+ cd MongoForge
30
+
31
+ # Create and activate a virtual environment
32
+ python -m venv env
33
+ env\Scripts\activate # Windows
34
+ # source env/bin/activate # macOS/Linux
35
+
36
+ # Install dependencies
37
+ pip install pymongo python-dotenv python-slugify
38
+
39
+ # For running tests (no live MongoDB needed)
40
+ pip install pytest mongomock
41
+ ```
42
+
43
+ ### Environment Setup
44
+
45
+ Create a `.env` file in the project root:
46
+
47
+ ```env
48
+ MONGO_URI=mongodb://localhost:27017/
49
+ DATABASE_NAME=mongoforge
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 🚀 Quick Start
55
+
56
+ ### Define a Model
57
+
58
+ ```python
59
+ from mongoforge import MongoForge
60
+ from mongoforge.fields import StringField, IntField, BoolField, DateTimeField, ListField
61
+
62
+ class User(MongoForge):
63
+ name = StringField(required=True, min_length=2, max_length=100)
64
+ email = StringField(required=True, regex=r"^[\w.+-]+@[\w-]+\.[\w.-]+$")
65
+ age = IntField(min_value=0, max_value=150)
66
+ is_admin = BoolField(default=False)
67
+ tags = ListField(StringField())
68
+ created = DateTimeField(auto_now_add=True)
69
+ updated = DateTimeField(auto_now=True)
70
+
71
+ class Meta:
72
+ collection = "users"
73
+ ```
74
+
75
+ ### Create & Validate
76
+
77
+ ```python
78
+ user = User(name="Saurabh", email="saurabh@example.com", age=25)
79
+
80
+ # Validate before saving
81
+ user.validate() # raises ValidationError if invalid
82
+
83
+ # Serialize to MongoDB document
84
+ doc = user.to_mongo()
85
+
86
+ # Insert into database
87
+ User.objects.create(doc)
88
+ ```
89
+
90
+ ### Query
91
+
92
+ ```python
93
+ # Get all documents
94
+ User.objects.all() # → list[dict]
95
+
96
+ # Filter with operators
97
+ User.objects.filter(age__gte=18) # age >= 18
98
+ User.objects.filter(name__in=["Alice", "Bob"])
99
+
100
+ # Get single document
101
+ User.objects.get({"email": "saurabh@example.com"})
102
+
103
+ # Count
104
+ User.objects.count()
105
+ ```
106
+
107
+ ### Update & Delete
108
+
109
+ ```python
110
+ # Update by ID
111
+ User.objects.update(id="...", update_data={"name": "Updated Name"})
112
+
113
+ # Update many
114
+ User.objects.update_many({"age": {"$gte": 30}}, {"is_admin": True})
115
+
116
+ # Delete
117
+ User.objects.delete_one({"name": "Alice"})
118
+ User.objects.delete_many({"age": {"$lt": 18}})
119
+ ```
120
+
121
+ ### Validation in Action
122
+
123
+ ```python
124
+ from mongoforge.exceptions import ValidationError
125
+
126
+ user = User(name="A", email="not-an-email", age=-5)
127
+ try:
128
+ user.validate()
129
+ except ValidationError as exc:
130
+ print(exc)
131
+ # Validation failed. name: Minimum length is 2; email: Value does not match regex ...; age: Minimum value is 0
132
+ print(exc.errors)
133
+ # {'name': ['Minimum length is 2'], 'email': ['Value does not match regex ...'], 'age': ['Minimum value is 0']}
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 📋 Field Types
139
+
140
+ | Field | Python Type | Extra Options |
141
+ |---|---|---|
142
+ | `StringField` | `str` | `min_length`, `max_length`, `regex` |
143
+ | `IntField` | `int` | `min_value`, `max_value` |
144
+ | `FloatField` | `float` | — |
145
+ | `BoolField` | `bool` | — |
146
+ | `ListField` | `list` | `field` (inner field for item validation) |
147
+ | `DateTimeField` | `datetime` | `auto_now`, `auto_now_add` |
148
+ | `DictField` | `dict` | — |
149
+ | `ObjectIdField` | `ObjectId` | Auto-generates if `None` |
150
+ | `EmbeddedField` | `dict` | `document_class` (stub — Phase 4) |
151
+
152
+ **Common options** (all fields): `required`, `default`, `unique`, `choices`, `validators`, `null`
153
+
154
+ ---
155
+
156
+ ## 🏗️ Architecture
157
+
158
+ ```
159
+ Model Class → MongoForgeMeta (metaclass) → collects fields + attaches MongoManager
160
+
161
+ ┌─────────────────────────────────────┘
162
+
163
+ MongoManager (cls.objects)
164
+ ├── CreateMixin → create(), bulk_create()
165
+ ├── ReadMixin → all(), first(), get(), filter(), count()
166
+ ├── UpdateMixin → update(), update_one(), update_many()
167
+ └── DeleteMixin → delete_one(), delete_many()
168
+ ```
169
+
170
+ ---
171
+
172
+ ## 🧪 Running Tests
173
+
174
+ Tests use [mongomock](https://github.com/mongomock/mongomock) — **no live MongoDB required**.
175
+
176
+ ```bash
177
+ # With venv activated
178
+ python -m pytest mongoforge/tests/ -v
179
+
180
+ # Or using venv python directly
181
+ env\Scripts\python.exe -m pytest mongoforge/tests/ -v
182
+ ```
183
+
184
+ **148 tests** covering:
185
+ - All 10 field types + validation paths
186
+ - Model instances, attribute access, serialization
187
+ - Metaclass, inheritance, abstract models
188
+ - Full CRUD operations + filter operators
189
+ - Exception hierarchy + formatting
190
+
191
+ ---
192
+
193
+ ## 🗺️ Roadmap
194
+
195
+ | Phase | Description | Status |
196
+ |---|---|---|
197
+ | **Phase 1** | Fields & Schema | ✅ ~95% |
198
+ | **Phase 2** | QuerySet (chainable queries) | ⬜ Not started |
199
+ | **Phase 3** | Model Lifecycle (`save()`, `delete()`, `reload()`, dirty tracking) | 🔶 ~50% |
200
+ | **Phase 4** | Relationships (EmbeddedDocument, ReferenceField) | ⬜ Not started |
201
+ | **Phase 5** | Indexes, Middleware & CLI | ⬜ Not started |
202
+ | **Phase 6** | Packaging & Developer Experience | 🔶 ~30% |
203
+
204
+ See [implementation_plan.md](implementation_plan.md) for the full roadmap with target APIs.
@@ -0,0 +1,5 @@
1
+ from .models import MongoForge
2
+
3
+ __all__ = [
4
+ "MongoForge"
5
+ ]
@@ -0,0 +1,19 @@
1
+ from pymongo import MongoClient
2
+ from pymongo.errors import PyMongoError
3
+
4
+ from .settings import MONGO_URI
5
+ from .exceptions import DatabaseConnectionError
6
+
7
+ _client = None
8
+
9
+ def get_client():
10
+ global _client
11
+ try:
12
+ if _client is None:
13
+ _client = MongoClient(MONGO_URI)
14
+ _client.admin.command("ping")
15
+ return _client
16
+ except PyMongoError as e:
17
+ raise DatabaseConnectionError(
18
+ f"Failed to connect to MongoDB: {e}"
19
+ ) from e
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict, List
4
+
5
+ class MongoForgeError(Exception):
6
+ """Base exception for all MongoForge errors."""
7
+
8
+
9
+ class DatabaseConnectionError(MongoForgeError):
10
+ """Raised when MongoForge cannot connect to MongoDB."""
11
+
12
+
13
+ class CollectionError(MongoForgeError):
14
+ """Raised when a collection-related operation fails."""
15
+
16
+
17
+ class ValidationError(MongoForgeError):
18
+ """
19
+ Raised when document validation fails.
20
+
21
+ Example:
22
+ raise ValidationError({
23
+ "email": ["This field is required"],
24
+ "age": ["Must be greater than 0"]
25
+ })
26
+ """
27
+
28
+ def __init__(self, errors: Dict[str, List[str]]) -> None:
29
+ self.errors = errors
30
+
31
+ message = self._build_message(errors)
32
+ super().__init__(message)
33
+
34
+ @staticmethod
35
+ def _build_message(errors: Dict[str, List[str]]) -> str:
36
+ if not errors:
37
+ return "Validation failed."
38
+
39
+ parts = []
40
+
41
+ for field, field_errors in errors.items():
42
+ if not field_errors:
43
+ continue
44
+
45
+ parts.append(f"{field}: {', '.join(field_errors)}")
46
+
47
+ if not parts:
48
+ return "Validation failed."
49
+
50
+ return f"Validation failed. {'; '.join(parts)}"
51
+
52
+
53
+ class DocumentNotFoundError(MongoForgeError):
54
+ """
55
+ Raised when a requested document cannot be found.
56
+ """
57
+
58
+ def __init__(self, message: str = "Document not found.") -> None:
59
+ super().__init__(message)
@@ -0,0 +1,24 @@
1
+ from .base import BaseField
2
+ from .types import (
3
+ StringField,
4
+ IntField,
5
+ FloatField,
6
+ BoolField,
7
+ DictField,
8
+ ObjectIdField,
9
+ ListField,
10
+ DateTimeField,
11
+ EmbeddedField,
12
+ )
13
+ __all__ = [
14
+ "BaseField",
15
+ "StringField",
16
+ "IntField",
17
+ "FloatField",
18
+ "BoolField",
19
+ "DictField",
20
+ "ObjectIdField",
21
+ "ListField",
22
+ "DateTimeField",
23
+ "EmbeddedField",
24
+ ]
@@ -0,0 +1,63 @@
1
+
2
+
3
+ class BaseField:
4
+ python_type = object
5
+
6
+ def __init__(
7
+ self,
8
+ *,
9
+ required=False,
10
+ default=None,
11
+ unique=False,
12
+ choices=None,
13
+ validators=None,
14
+ null=False,
15
+ ):
16
+ self.name = None
17
+ self.required = required
18
+ self.default = default
19
+ self.unique = unique
20
+ self.choices = choices or []
21
+ self.validators = validators or []
22
+ self.null = null
23
+
24
+ def get_default(self):
25
+ if callable(self.default):
26
+ return self.default()
27
+ return self.default
28
+
29
+ def to_python(self, value):
30
+ return value
31
+
32
+ def to_mongo(self, value):
33
+ return value
34
+
35
+ def validate(self, value):
36
+ errors = []
37
+
38
+ if value is None:
39
+ if self.required and not self.null:
40
+ errors.append("This field is required.")
41
+ return errors
42
+
43
+ if (
44
+ self.python_type is not object
45
+ and not isinstance(value, self.python_type)
46
+ ):
47
+ errors.append(
48
+ f"Expected {self.python_type.__name__}, got {type(value).__name__}"
49
+ )
50
+ return errors
51
+
52
+ if self.choices and value not in self.choices:
53
+ errors.append(
54
+ f"Value must be one of: {self.choices}"
55
+ )
56
+
57
+ for validator in self.validators:
58
+ try:
59
+ validator(value)
60
+ except ValueError as e:
61
+ errors.append(str(e))
62
+
63
+ return errors