python-ppa 0.1.4__tar.gz → 0.1.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-ppa
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: JPA-like ORM for Python
5
5
  Author: Pratyush Kumar
6
6
  License-Expression: MIT
@@ -20,9 +20,9 @@ Requires-Dist: typing-inspection==0.4.2
20
20
  Requires-Dist: typing_extensions==4.15.0
21
21
  Dynamic: license-file
22
22
 
23
- # python-jpa
23
+ # python-ppa
24
24
 
25
- `python-jpa` is a lightweight, Spring Data JPA-inspired Object-Document Mapper (ODM) for MongoDB built on top of **Python 3.13+**, **Pydantic v2**, and **PyMongo**.
25
+ `python-ppa` is a lightweight, Spring Data ppa-inspired Object-Document Mapper (ODM) for MongoDB built on top of **Python 3.13+**, **Pydantic v2**, and **PyMongo**.
26
26
 
27
27
  It uses advanced Python metaprogramming (`metaclass`) to dynamically generate MongoDB queries at runtime based on method naming conventions or explicit query declarations, drastically reducing boilerplate code.
28
28
 
@@ -30,7 +30,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
30
30
 
31
31
  ## 🚀 Features
32
32
 
33
- * **JPA-Like Repository Pattern:** Declare an interface, and let the metaclass handle query assembly dynamically.
33
+ * **ppa-Like Repository Pattern:** Declare an interface, and let the metaclass handle query assembly dynamically.
34
34
  * **Query Naming Conventions:** Instantly supports `find_by_*`, `find_all_by_*`, `exists_by_*`, and `count_by_*` pattern resolutions.
35
35
  * **Custom Query Annotations:** Bind complex MongoDB query templates with custom parameter injections using the `@query` decorator.
36
36
  * **Data Validation:** Fully powered by Pydantic v2 for robust runtime type checking and parsing.
@@ -44,7 +44,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
44
44
  ```text
45
45
  .
46
46
  ├── README.md
47
- ├── jpa
47
+ ├── ppa
48
48
  │ ├── __init__.py
49
49
  │ ├── config.py # App bootstrapping, YAML parsing & environment mapping
50
50
  │ └── mongo
@@ -57,7 +57,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
57
57
  Prerequisites
58
58
  • Python 3.13 or higher
59
59
  Installation & Virtual Env Setup
60
- 1. Clone the repository and navigate to its root: cd python-jpa
60
+ 1. Clone the repository and navigate to its root: cd python-ppa
61
61
  2. Spin up a virtual environment and update your pip core dependencies: python3 -m venv venv source venv/bin/activate # On Windows use: venv\Scripts\activate pip install --upgrade pip
62
62
  3. Install the project library dependencies: pip install -r requirements.txt
63
63
  ⚙️ Configuration Management
@@ -70,7 +70,7 @@ app:
70
70
  2. Environment Profile Configuration (resources/settings-dev.yml)
71
71
  mongodb:
72
72
  uri: ${MONGO_URI:mongodb://localhost:27017}
73
- database: ${MONGO_DB_NAME:jpa_database}
73
+ database: ${MONGO_DB_NAME:ppa_database}
74
74
 
75
75
  framework:
76
76
  logging:
@@ -81,7 +81,7 @@ framework:
81
81
  Here is a quick overview of how you can configure a domain entity and generate auto-implemented interface pipelines:
82
82
  1. Define your Document Model
83
83
  from pydantic import Field
84
- from jpa.mongo.interface import DocumentModel, document
84
+ from ppa.mongo.interface import DocumentModel, document
85
85
 
86
86
  @document(name="users")
87
87
  class User(DocumentModel):
@@ -92,7 +92,7 @@ class User(DocumentModel):
92
92
 
93
93
  2. Declare your Interface Repository
94
94
  By extending IRepository[T], method naming patterns are captured and converted into database interactions seamlessly.
95
- from jpa.mongo.interface import IRepository, query
95
+ from ppa.mongo.interface import IRepository, query
96
96
  from typing import List, Optional
97
97
 
98
98
  class UserRepository(IRepository[User]):
@@ -107,7 +107,7 @@ class UserRepository(IRepository[User]):
107
107
  def find_by_email_and_min_age(self, email: str, min_age: int) -> List[User]: ...
108
108
 
109
109
  3. Execute CRUD Actions
110
- from jpa.config import close_db_connection
110
+ from ppa.config import close_db_connection
111
111
 
112
112
  # Initialize repository instance
113
113
  user_repo = UserRepository()
@@ -1,6 +1,6 @@
1
- # python-jpa
1
+ # python-ppa
2
2
 
3
- `python-jpa` is a lightweight, Spring Data JPA-inspired Object-Document Mapper (ODM) for MongoDB built on top of **Python 3.13+**, **Pydantic v2**, and **PyMongo**.
3
+ `python-ppa` is a lightweight, Spring Data ppa-inspired Object-Document Mapper (ODM) for MongoDB built on top of **Python 3.13+**, **Pydantic v2**, and **PyMongo**.
4
4
 
5
5
  It uses advanced Python metaprogramming (`metaclass`) to dynamically generate MongoDB queries at runtime based on method naming conventions or explicit query declarations, drastically reducing boilerplate code.
6
6
 
@@ -8,7 +8,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
8
8
 
9
9
  ## 🚀 Features
10
10
 
11
- * **JPA-Like Repository Pattern:** Declare an interface, and let the metaclass handle query assembly dynamically.
11
+ * **ppa-Like Repository Pattern:** Declare an interface, and let the metaclass handle query assembly dynamically.
12
12
  * **Query Naming Conventions:** Instantly supports `find_by_*`, `find_all_by_*`, `exists_by_*`, and `count_by_*` pattern resolutions.
13
13
  * **Custom Query Annotations:** Bind complex MongoDB query templates with custom parameter injections using the `@query` decorator.
14
14
  * **Data Validation:** Fully powered by Pydantic v2 for robust runtime type checking and parsing.
@@ -22,7 +22,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
22
22
  ```text
23
23
  .
24
24
  ├── README.md
25
- ├── jpa
25
+ ├── ppa
26
26
  │ ├── __init__.py
27
27
  │ ├── config.py # App bootstrapping, YAML parsing & environment mapping
28
28
  │ └── mongo
@@ -35,7 +35,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
35
35
  Prerequisites
36
36
  • Python 3.13 or higher
37
37
  Installation & Virtual Env Setup
38
- 1. Clone the repository and navigate to its root: cd python-jpa
38
+ 1. Clone the repository and navigate to its root: cd python-ppa
39
39
  2. Spin up a virtual environment and update your pip core dependencies: python3 -m venv venv source venv/bin/activate # On Windows use: venv\Scripts\activate pip install --upgrade pip
40
40
  3. Install the project library dependencies: pip install -r requirements.txt
41
41
  ⚙️ Configuration Management
@@ -48,7 +48,7 @@ app:
48
48
  2. Environment Profile Configuration (resources/settings-dev.yml)
49
49
  mongodb:
50
50
  uri: ${MONGO_URI:mongodb://localhost:27017}
51
- database: ${MONGO_DB_NAME:jpa_database}
51
+ database: ${MONGO_DB_NAME:ppa_database}
52
52
 
53
53
  framework:
54
54
  logging:
@@ -59,7 +59,7 @@ framework:
59
59
  Here is a quick overview of how you can configure a domain entity and generate auto-implemented interface pipelines:
60
60
  1. Define your Document Model
61
61
  from pydantic import Field
62
- from jpa.mongo.interface import DocumentModel, document
62
+ from ppa.mongo.interface import DocumentModel, document
63
63
 
64
64
  @document(name="users")
65
65
  class User(DocumentModel):
@@ -70,7 +70,7 @@ class User(DocumentModel):
70
70
 
71
71
  2. Declare your Interface Repository
72
72
  By extending IRepository[T], method naming patterns are captured and converted into database interactions seamlessly.
73
- from jpa.mongo.interface import IRepository, query
73
+ from ppa.mongo.interface import IRepository, query
74
74
  from typing import List, Optional
75
75
 
76
76
  class UserRepository(IRepository[User]):
@@ -85,7 +85,7 @@ class UserRepository(IRepository[User]):
85
85
  def find_by_email_and_min_age(self, email: str, min_age: int) -> List[User]: ...
86
86
 
87
87
  3. Execute CRUD Actions
88
- from jpa.config import close_db_connection
88
+ from ppa.config import close_db_connection
89
89
 
90
90
  # Initialize repository instance
91
91
  user_repo = UserRepository()
@@ -0,0 +1,4 @@
1
+ from ppa.mongo.interface import IRepository
2
+ from ppa.mongo.interface import query
3
+ from ppa.mongo.interface import document
4
+ from ppa.mongo.interface import DocumentModel
@@ -1,27 +1,64 @@
1
1
  import inspect
2
2
  import re
3
- from typing import Any, Callable, ClassVar, Generic, TypeVar, cast, get_args, get_origin, get_type_hints, List
4
- from unittest import result
3
+ from typing import Any, Callable, ClassVar, Generic, Optional, TypeVar, cast, get_args, get_origin, get_type_hints, List
5
4
 
6
- from pydantic import BaseModel, ConfigDict
5
+ from pydantic import BaseModel, ConfigDict, Field
7
6
  from bson import ObjectId, errors
8
7
 
9
8
  from ppa.config import get_collection, get_framework_logger
10
9
 
11
- T = TypeVar("T", bound=BaseModel)
10
+ T = TypeVar("T", bound='DocumentModel')
12
11
  F = TypeVar("F", bound=Callable[..., Any])
13
12
  framework_logger = get_framework_logger("repository")
14
13
 
15
- DEFAULT_DOCUMENT_CONFIG = dict[str, Any] = {
16
- "populate_by_name": True,
17
- "arbitrary_types_allowed": True,
18
- "json_encoders": {ObjectId: str}
19
- }
20
-
21
14
 
22
15
  class DocumentModel(BaseModel):
16
+ """
17
+ Base class for all MongoDB models.
18
+ Inherits default configurations and the standard MongoDB _id mapping.
19
+ """
23
20
  __collection_name__: ClassVar[str]
24
21
 
22
+ # Automatically inherited and merged by child models
23
+ model_config = ConfigDict(
24
+ populate_by_name=True,
25
+ arbitrary_types_allowed=True,
26
+ json_encoders={ObjectId: str}
27
+ )
28
+
29
+ # Universal MongoDB _id mapping
30
+ id: Optional[ObjectId] = Field(default=None, alias="_id")
31
+
32
+ def model_dump(self, **kwargs: Any) -> dict[str, Any]:
33
+ """
34
+ Overrides default Pydantic dump to ensure MongoDB compatibility.
35
+ Defaults to excluding None values and using aliases (id -> _id).
36
+ """
37
+ kwargs.setdefault("exclude_none", True)
38
+ kwargs.setdefault("by_alias", True)
39
+ return super().model_dump(**kwargs)
40
+
41
+
42
+ # Inherit from list[T] so we can use DocumentList[User], DocumentList[Booking], etc.
43
+ class DocumentList(list[T]):
44
+ """
45
+ List of documents that can be used to perform bulk operations.
46
+ Inherits default configurations
47
+ """
48
+
49
+ def model_dump(self, **kwargs: Any) -> list[dict[str, Any]]:
50
+ return [doc.model_dump(**kwargs) for doc in self]
51
+
52
+ @classmethod
53
+ def model_validate(cls, obj: list[Any], entity_cls: type[T]) -> 'DocumentList[T]':
54
+ """
55
+ Validates a list of raw dictionaries and returns a populated DocumentList.
56
+ We pass `entity_cls` because generic type T is erased at runtime.
57
+ """
58
+ if not isinstance(obj, list) and not hasattr(obj, "__iter__"):
59
+ raise ValueError("Data provided to DocumentList.model_validate must be iterable.")
60
+
61
+ return cls([entity_cls.model_validate(item) for item in obj])
25
62
 
26
63
  def query(definition: dict[str, Any]) -> Callable[[F], F]:
27
64
  """Annotation to specify a custom MongoDB query template."""
@@ -38,20 +75,6 @@ def document(name: str) -> Callable[[type[DocumentModel]], type[DocumentModel]]:
38
75
 
39
76
  def decorator(cls):
40
77
  cls.__collection_name__ = name
41
- current_cfs = dict(getattr(cls, "model_config", {}) or {})
42
- current_encoders = dict(current_cfs.get("json_encoders", {}) or {})
43
- merged_encoders = {
44
- **DEFAULT_DOCUMENT_CONFIG["json_encoders"],
45
- **current_encoders
46
- }
47
- merged_cfs = {
48
- **DEFAULT_DOCUMENT_CONFIG["model_config"],
49
- **current_cfs,
50
- "json_encoders": merged_encoders,
51
- }
52
- cls.model_config = ConfigDict(**merged_cfs)
53
- if hasattr(cls, "model_rebuild"):
54
- cls.model_rebuild(force=True)
55
78
  return cls
56
79
 
57
80
  return decorator
@@ -86,8 +109,8 @@ class RepositoryMeta(type):
86
109
  is_convention = re.match(r"(find_by|find_all_by|exists_by|count_by)_(.*)", attr_name)
87
110
 
88
111
  if not is_convention:
89
- framework_logger.info("Convention does not exists, relying on user implementation")
90
- return cls
112
+ framework_logger.info("Convention does not exist, relying on user implementation")
113
+ continue
91
114
 
92
115
  if query_template or is_convention is not None:
93
116
  hints = get_type_hints(attr_value)
@@ -139,12 +162,11 @@ class RepositoryMeta(type):
139
162
  if p_name in bound.arguments:
140
163
  ordered_args.append(bound.arguments[p_name])
141
164
 
142
- # Pure dictionary walk for placeholder substitution (no internal casting strings to dates)
143
165
  def substitute_placeholders(node: Any) -> Any:
144
166
  if isinstance(node, str) and node.startswith("?"):
145
167
  try:
146
168
  idx = int(node[1:])
147
- return ordered_args[idx] # Inject whatever object type the user passed
169
+ return ordered_args[idx]
148
170
  except (ValueError, IndexError):
149
171
  return node
150
172
  elif isinstance(node, dict):
@@ -193,6 +215,13 @@ class RepositoryMeta(type):
193
215
  bound.apply_defaults()
194
216
  actual_value = bound.arguments.get(param_names[0]) if param_names else None
195
217
 
218
+ # Convert string IDs to ObjectId if querying against _id
219
+ if mongo_field == "_id" and isinstance(actual_value, str):
220
+ try:
221
+ actual_value = ObjectId(actual_value)
222
+ except errors.InvalidId:
223
+ pass
224
+
196
225
  query_dict: dict[str, Any] = {mongo_field: actual_value}
197
226
 
198
227
  framework_logger.debug(
@@ -235,7 +264,6 @@ def execute_dynamic_query(
235
264
  return_type: Any,
236
265
  entity_cls: type[DocumentModel],
237
266
  ) -> Any:
238
- # Only cast fields checking against the database's internal _id field
239
267
  processed_query = cast_primary_keys(query_dict)
240
268
 
241
269
  framework_logger.debug(
@@ -253,18 +281,15 @@ def execute_dynamic_query(
253
281
  if strategy == "exists_by" or origin_type is bool:
254
282
  return collection.count_documents(processed_query, limit=1) > 0
255
283
 
256
- def sanitize_output(doc: Any) -> Any:
257
- if doc and "_id" in doc:
258
- doc = dict(doc)
259
- doc["_id"] = str(doc["_id"])
260
- return doc
261
-
262
- if origin_type is list:
284
+ # UPDATE: Support both standard lists and our new DocumentList
285
+ if origin_type in (list, DocumentList):
263
286
  cursor = collection.find(processed_query)
264
- return [entity_cls.model_validate(sanitize_output(doc)) for doc in cursor]
287
+ results = [entity_cls.model_validate(doc) for doc in cursor]
288
+ # Return the specific list type requested
289
+ return DocumentList(results) if origin_type is DocumentList else results
265
290
 
266
291
  doc = collection.find_one(processed_query)
267
- return entity_cls.model_validate(sanitize_output(doc)) if doc else None
292
+ return entity_cls.model_validate(doc) if doc else None
268
293
 
269
294
 
270
295
  class IRepository(Generic[T], metaclass=RepositoryMeta):
@@ -284,43 +309,31 @@ class IRepository(Generic[T], metaclass=RepositoryMeta):
284
309
  )
285
310
 
286
311
  def save(self, pyd_model: T) -> str:
287
- data = pyd_model.model_dump(exclude_none=True)
312
+ # Base model override handles exclude_none and by_alias automatically
313
+ data = pyd_model.model_dump()
288
314
  inserted_data = self.collection.insert_one(data)
289
- return inserted_data.inserted_id
315
+ return str(inserted_data.inserted_id)
290
316
 
291
317
  def save_all(self, pyd_models: List[T]) -> List[str]:
292
- data = [pyd_model for pyd_model in pyd_models]
318
+ data = [pyd_model.model_dump() for pyd_model in pyd_models]
293
319
  inserted_data = self.collection.insert_many(data)
294
- return inserted_data.inserted_ids
320
+ return [str(obj_id) for obj_id in inserted_data.inserted_ids]
295
321
 
296
- def find_all(self) -> list[T]:
297
- """
298
- Fetches all documents matching a query and maps them safely to
299
- the repository's bound Pydantic model.
300
- """
301
- # 1. Fetch raw cursor streams from MongoDB
322
+ def find_all(self) -> DocumentList[T]:
302
323
  cursor = self.collection.find()
303
-
304
- # 2. Local serialization helper to clean BSON primary keys
305
- # def sanitize(doc):
306
- # if doc and "_id" in doc:
307
- # doc = dict(doc)
308
- # doc["_id"] = str(doc["_id"])
309
- # return doc
310
-
311
- # 3. Instantiate using self._entity_cls (which holds the real model at runtime)
312
- return [self._entity_cls(**dict(data_dict)) for data_dict in cursor]
324
+ # UPDATE: Wrap the list comprehension in DocumentList
325
+ return DocumentList([self._entity_cls.model_validate(doc) for doc in cursor])
313
326
 
314
327
  def find(self, query_dict: dict) -> T:
315
- data = self.collection.find(query_dict)
328
+ data = self.collection.find_one(query_dict)
316
329
  if not data:
317
330
  raise ValueError("No data found")
318
- return dict(data)
331
+ return self._entity_cls.model_validate(data)
319
332
 
320
333
  def delete(self, resource_id: str) -> bool:
321
334
  query_result = self.collection.delete_one({"_id": ObjectId(resource_id)})
322
335
  return query_result.deleted_count > 0
323
336
 
324
- def update(self, resource_id: str, data:dict):
337
+ def update(self, resource_id: str, data: dict) -> bool:
325
338
  query_result = self.collection.update_one({"_id": ObjectId(resource_id)}, {"$set": data})
326
339
  return query_result.modified_count > 0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "python-ppa"
7
- version = "0.1.4"
7
+ version = "0.1.6"
8
8
  description = "JPA-like ORM for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-ppa
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: JPA-like ORM for Python
5
5
  Author: Pratyush Kumar
6
6
  License-Expression: MIT
@@ -20,9 +20,9 @@ Requires-Dist: typing-inspection==0.4.2
20
20
  Requires-Dist: typing_extensions==4.15.0
21
21
  Dynamic: license-file
22
22
 
23
- # python-jpa
23
+ # python-ppa
24
24
 
25
- `python-jpa` is a lightweight, Spring Data JPA-inspired Object-Document Mapper (ODM) for MongoDB built on top of **Python 3.13+**, **Pydantic v2**, and **PyMongo**.
25
+ `python-ppa` is a lightweight, Spring Data ppa-inspired Object-Document Mapper (ODM) for MongoDB built on top of **Python 3.13+**, **Pydantic v2**, and **PyMongo**.
26
26
 
27
27
  It uses advanced Python metaprogramming (`metaclass`) to dynamically generate MongoDB queries at runtime based on method naming conventions or explicit query declarations, drastically reducing boilerplate code.
28
28
 
@@ -30,7 +30,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
30
30
 
31
31
  ## 🚀 Features
32
32
 
33
- * **JPA-Like Repository Pattern:** Declare an interface, and let the metaclass handle query assembly dynamically.
33
+ * **ppa-Like Repository Pattern:** Declare an interface, and let the metaclass handle query assembly dynamically.
34
34
  * **Query Naming Conventions:** Instantly supports `find_by_*`, `find_all_by_*`, `exists_by_*`, and `count_by_*` pattern resolutions.
35
35
  * **Custom Query Annotations:** Bind complex MongoDB query templates with custom parameter injections using the `@query` decorator.
36
36
  * **Data Validation:** Fully powered by Pydantic v2 for robust runtime type checking and parsing.
@@ -44,7 +44,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
44
44
  ```text
45
45
  .
46
46
  ├── README.md
47
- ├── jpa
47
+ ├── ppa
48
48
  │ ├── __init__.py
49
49
  │ ├── config.py # App bootstrapping, YAML parsing & environment mapping
50
50
  │ └── mongo
@@ -57,7 +57,7 @@ It uses advanced Python metaprogramming (`metaclass`) to dynamically generate Mo
57
57
  Prerequisites
58
58
  • Python 3.13 or higher
59
59
  Installation & Virtual Env Setup
60
- 1. Clone the repository and navigate to its root: cd python-jpa
60
+ 1. Clone the repository and navigate to its root: cd python-ppa
61
61
  2. Spin up a virtual environment and update your pip core dependencies: python3 -m venv venv source venv/bin/activate # On Windows use: venv\Scripts\activate pip install --upgrade pip
62
62
  3. Install the project library dependencies: pip install -r requirements.txt
63
63
  ⚙️ Configuration Management
@@ -70,7 +70,7 @@ app:
70
70
  2. Environment Profile Configuration (resources/settings-dev.yml)
71
71
  mongodb:
72
72
  uri: ${MONGO_URI:mongodb://localhost:27017}
73
- database: ${MONGO_DB_NAME:jpa_database}
73
+ database: ${MONGO_DB_NAME:ppa_database}
74
74
 
75
75
  framework:
76
76
  logging:
@@ -81,7 +81,7 @@ framework:
81
81
  Here is a quick overview of how you can configure a domain entity and generate auto-implemented interface pipelines:
82
82
  1. Define your Document Model
83
83
  from pydantic import Field
84
- from jpa.mongo.interface import DocumentModel, document
84
+ from ppa.mongo.interface import DocumentModel, document
85
85
 
86
86
  @document(name="users")
87
87
  class User(DocumentModel):
@@ -92,7 +92,7 @@ class User(DocumentModel):
92
92
 
93
93
  2. Declare your Interface Repository
94
94
  By extending IRepository[T], method naming patterns are captured and converted into database interactions seamlessly.
95
- from jpa.mongo.interface import IRepository, query
95
+ from ppa.mongo.interface import IRepository, query
96
96
  from typing import List, Optional
97
97
 
98
98
  class UserRepository(IRepository[User]):
@@ -107,7 +107,7 @@ class UserRepository(IRepository[User]):
107
107
  def find_by_email_and_min_age(self, email: str, min_age: int) -> List[User]: ...
108
108
 
109
109
  3. Execute CRUD Actions
110
- from jpa.config import close_db_connection
110
+ from ppa.config import close_db_connection
111
111
 
112
112
  # Initialize repository instance
113
113
  user_repo = UserRepository()
@@ -1,3 +0,0 @@
1
- from ppa.mongo.interface import IRepository
2
- from ppa.mongo.interface import query
3
- from ppa.mongo.interface import document
File without changes
File without changes
File without changes
File without changes