beanis 0.0.1__py3-none-any.whl

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 (75) hide show
  1. beanis/__init__.py +50 -0
  2. beanis/exceptions.py +74 -0
  3. beanis/executors/__init__.py +0 -0
  4. beanis/executors/migrate.py +221 -0
  5. beanis/migrations/__init__.py +0 -0
  6. beanis/migrations/controllers/__init__.py +0 -0
  7. beanis/migrations/controllers/base.py +18 -0
  8. beanis/migrations/controllers/free_fall.py +28 -0
  9. beanis/migrations/controllers/iterative.py +130 -0
  10. beanis/migrations/database.py +16 -0
  11. beanis/migrations/models.py +33 -0
  12. beanis/migrations/runner.py +240 -0
  13. beanis/migrations/template.py +6 -0
  14. beanis/migrations/utils.py +7 -0
  15. beanis/odm/__init__.py +0 -0
  16. beanis/odm/actions.py +257 -0
  17. beanis/odm/cache.py +45 -0
  18. beanis/odm/custom_types/__init__.py +10 -0
  19. beanis/odm/custom_types/bson/__init__.py +0 -0
  20. beanis/odm/custom_types/bson/binary.py +0 -0
  21. beanis/odm/custom_types/decimal.py +12 -0
  22. beanis/odm/documents.py +431 -0
  23. beanis/odm/enums.py +10 -0
  24. beanis/odm/fields.py +158 -0
  25. beanis/odm/interfaces/__init__.py +0 -0
  26. beanis/odm/interfaces/clone.py +6 -0
  27. beanis/odm/interfaces/detector.py +13 -0
  28. beanis/odm/interfaces/find.py +480 -0
  29. beanis/odm/interfaces/getters.py +28 -0
  30. beanis/odm/interfaces/inheritance.py +19 -0
  31. beanis/odm/interfaces/session.py +19 -0
  32. beanis/odm/interfaces/setters.py +28 -0
  33. beanis/odm/models.py +24 -0
  34. beanis/odm/operators/__init__.py +40 -0
  35. beanis/odm/operators/find/__init__.py +7 -0
  36. beanis/odm/operators/find/array.py +121 -0
  37. beanis/odm/operators/find/bitwise.py +60 -0
  38. beanis/odm/operators/find/comparison.py +227 -0
  39. beanis/odm/operators/find/element.py +76 -0
  40. beanis/odm/operators/find/evaluation.py +193 -0
  41. beanis/odm/operators/find/geospatial.py +316 -0
  42. beanis/odm/operators/find/logical.py +166 -0
  43. beanis/odm/operators/update/__init__.py +11 -0
  44. beanis/odm/operators/update/array.py +142 -0
  45. beanis/odm/operators/update/bitwise.py +23 -0
  46. beanis/odm/operators/update/general.py +234 -0
  47. beanis/odm/registry.py +31 -0
  48. beanis/odm/settings/__init__.py +0 -0
  49. beanis/odm/settings/base.py +21 -0
  50. beanis/odm/settings/document.py +38 -0
  51. beanis/odm/settings/timeseries.py +43 -0
  52. beanis/odm/settings/union_doc.py +5 -0
  53. beanis/odm/settings/view.py +13 -0
  54. beanis/odm/utils/__init__.py +0 -0
  55. beanis/odm/utils/dump.py +50 -0
  56. beanis/odm/utils/encoder.py +142 -0
  57. beanis/odm/utils/find.py +403 -0
  58. beanis/odm/utils/general.py +0 -0
  59. beanis/odm/utils/init.py +311 -0
  60. beanis/odm/utils/parsing.py +148 -0
  61. beanis/odm/utils/projection.py +36 -0
  62. beanis/odm/utils/pydantic.py +62 -0
  63. beanis/odm/utils/relations.py +50 -0
  64. beanis/odm/utils/self_validation.py +21 -0
  65. beanis/odm/utils/state.py +89 -0
  66. beanis/odm/utils/typing.py +74 -0
  67. beanis/operators.py +118 -0
  68. beanis/py.typed +0 -0
  69. beanis/pydantic_check.py +5 -0
  70. beanis/test.py +47 -0
  71. beanis-0.0.1.dist-info/LICENSE +201 -0
  72. beanis-0.0.1.dist-info/METADATA +135 -0
  73. beanis-0.0.1.dist-info/RECORD +75 -0
  74. beanis-0.0.1.dist-info/WHEEL +4 -0
  75. beanis-0.0.1.dist-info/entry_points.txt +3 -0
beanis/__init__.py ADDED
@@ -0,0 +1,50 @@
1
+ from beanis.migrations.controllers.free_fall import free_fall_migration
2
+ from beanis.migrations.controllers.iterative import iterative_migration
3
+ from beanis.odm.actions import (
4
+ After,
5
+ Before,
6
+ Delete,
7
+ Insert,
8
+ Replace,
9
+ Save,
10
+ SaveChanges,
11
+ Update,
12
+ ValidateOnSave,
13
+ after_event,
14
+ before_event,
15
+ )
16
+ from beanis.odm.custom_types import DecimalAnnotation
17
+ from beanis.odm.documents import (
18
+ Document,
19
+ MergeStrategy,
20
+ )
21
+
22
+ from beanis.odm.settings.timeseries import Granularity, TimeSeriesConfig
23
+ from beanis.odm.utils.init import init_beanis
24
+
25
+ __version__ = "0.0.1"
26
+ __all__ = [
27
+ # ODM
28
+ "Document",
29
+ "init_beanis",
30
+ "TimeSeriesConfig",
31
+ "Granularity",
32
+ "MergeStrategy",
33
+ # Actions
34
+ "before_event",
35
+ "after_event",
36
+ "Insert",
37
+ "Replace",
38
+ "Save",
39
+ "SaveChanges",
40
+ "ValidateOnSave",
41
+ "Delete",
42
+ "Before",
43
+ "After",
44
+ "Update",
45
+ # Migrations
46
+ "iterative_migration",
47
+ "free_fall_migration",
48
+ # Custom Types
49
+ "DecimalAnnotation",
50
+ ]
beanis/exceptions.py ADDED
@@ -0,0 +1,74 @@
1
+ class WrongDocumentUpdateStrategy(Exception):
2
+ pass
3
+
4
+
5
+ class DocumentNotFound(Exception):
6
+ pass
7
+
8
+
9
+ class DocumentAlreadyCreated(Exception):
10
+ pass
11
+
12
+
13
+ class DocumentWasNotSaved(Exception):
14
+ pass
15
+
16
+
17
+ class CollectionWasNotInitialized(Exception):
18
+ pass
19
+
20
+
21
+ class MigrationException(Exception):
22
+ pass
23
+
24
+
25
+ class ReplaceError(Exception):
26
+ pass
27
+
28
+
29
+ class StateManagementIsTurnedOff(Exception):
30
+ pass
31
+
32
+
33
+ class StateNotSaved(Exception):
34
+ pass
35
+
36
+
37
+ class RevisionIdWasChanged(Exception):
38
+ pass
39
+
40
+
41
+ class NotSupported(Exception):
42
+ pass
43
+
44
+
45
+ class MongoDBVersionError(Exception):
46
+ pass
47
+
48
+
49
+ class ViewWasNotInitialized(Exception):
50
+ pass
51
+
52
+
53
+ class ViewHasNoSettings(Exception):
54
+ pass
55
+
56
+
57
+ class UnionHasNoRegisteredDocs(Exception):
58
+ pass
59
+
60
+
61
+ class UnionDocNotInited(Exception):
62
+ pass
63
+
64
+
65
+ class DocWasNotRegisteredInUnionClass(Exception):
66
+ pass
67
+
68
+
69
+ class Deprecation(Exception):
70
+ pass
71
+
72
+
73
+ class ApplyChangesException(Exception):
74
+ pass
File without changes
@@ -0,0 +1,221 @@
1
+ import asyncio
2
+ import logging
3
+ import os
4
+ import shutil
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ import click
10
+ import toml
11
+
12
+ from beanis.migrations import template
13
+ from beanis.migrations.database import DBHandler
14
+ from beanis.migrations.models import RunningDirections, RunningMode
15
+ from beanis.migrations.runner import MigrationNode
16
+
17
+ logging.basicConfig(format="%(message)s", level=logging.INFO)
18
+
19
+
20
+ class MigrationSettings:
21
+ def __init__(self, **kwargs):
22
+ self.direction = (
23
+ kwargs.get("direction")
24
+ or self.get_env_value("direction")
25
+ or self.get_from_toml("direction")
26
+ or RunningDirections.FORWARD
27
+ )
28
+
29
+ self.distance = int(
30
+ kwargs.get("distance")
31
+ or self.get_env_value("distance")
32
+ or self.get_from_toml("distance")
33
+ or 0
34
+ )
35
+ self.connection_uri = str(
36
+ kwargs.get("connection_uri")
37
+ or self.get_env_value("connection_uri")
38
+ or self.get_from_toml("connection_uri")
39
+ )
40
+ self.database_name = str(
41
+ kwargs.get("database_name")
42
+ or self.get_env_value("database_name")
43
+ or self.get_from_toml("database_name")
44
+ )
45
+ self.path = Path(
46
+ kwargs.get("path")
47
+ or self.get_env_value("path")
48
+ or self.get_from_toml("path")
49
+ )
50
+ self.allow_index_dropping = bool(
51
+ kwargs.get("allow_index_dropping")
52
+ or self.get_env_value("allow_index_dropping")
53
+ or self.get_from_toml("allow_index_dropping")
54
+ or False
55
+ )
56
+ self.use_transaction = bool(kwargs.get("use_transaction"))
57
+
58
+ @staticmethod
59
+ def get_env_value(field_name) -> Any:
60
+ if field_name == "connection_uri":
61
+ value = (
62
+ os.environ.get("BEANIE_URI")
63
+ or os.environ.get("BEANIE_CONNECTION_URI")
64
+ or os.environ.get("BEANIE_CONNECTION_STRING")
65
+ or os.environ.get("BEANIE_MONGODB_DSN")
66
+ or os.environ.get("BEANIE_MONGODB_URI")
67
+ or os.environ.get("beanie_uri")
68
+ or os.environ.get("beanie_connection_uri")
69
+ or os.environ.get("beanie_connection_string")
70
+ or os.environ.get("beanie_mongodb_dsn")
71
+ or os.environ.get("beanie_mongodb_uri")
72
+ )
73
+ elif field_name == "database_name":
74
+ value = (
75
+ os.environ.get("BEANIE_DB")
76
+ or os.environ.get("BEANIE_DB_NAME")
77
+ or os.environ.get("BEANIE_DATABASE_NAME")
78
+ or os.environ.get("beanie_db")
79
+ or os.environ.get("beanie_db_name")
80
+ or os.environ.get("beanie_database_name")
81
+ )
82
+ else:
83
+ value = os.environ.get(
84
+ f"BEANIE_{field_name.upper()}"
85
+ ) or os.environ.get(f"beanie_{field_name.lower()}")
86
+ return value
87
+
88
+ @staticmethod
89
+ def get_from_toml(field_name) -> Any:
90
+ path = Path("pyproject.toml")
91
+ if path.is_file():
92
+ val = (
93
+ toml.load(path)
94
+ .get("tool", {})
95
+ .get("beanis", {})
96
+ .get("migrations", {})
97
+ )
98
+ else:
99
+ val = {}
100
+ return val.get(field_name)
101
+
102
+
103
+ @click.group()
104
+ def migrations():
105
+ pass
106
+
107
+
108
+ async def run_migrate(settings: MigrationSettings):
109
+ DBHandler.set_db(settings.connection_uri, settings.database_name)
110
+ root = await MigrationNode.build(settings.path)
111
+ mode = RunningMode(
112
+ direction=settings.direction, distance=settings.distance
113
+ )
114
+ await root.run(
115
+ mode=mode,
116
+ allow_index_dropping=settings.allow_index_dropping,
117
+ use_transaction=settings.use_transaction,
118
+ )
119
+
120
+
121
+ @migrations.command()
122
+ @click.option(
123
+ "--forward",
124
+ "direction",
125
+ required=False,
126
+ flag_value="FORWARD",
127
+ help="Roll the migrations forward. This is default",
128
+ )
129
+ @click.option(
130
+ "--backward",
131
+ "direction",
132
+ required=False,
133
+ flag_value="BACKWARD",
134
+ help="Roll the migrations backward",
135
+ )
136
+ @click.option(
137
+ "-d",
138
+ "--distance",
139
+ required=False,
140
+ help="How many migrations should be done since the current? "
141
+ "0 - all the migrations. Default is 0",
142
+ )
143
+ @click.option(
144
+ "-uri",
145
+ "--connection-uri",
146
+ required=False,
147
+ type=str,
148
+ help="MongoDB connection URI",
149
+ )
150
+ @click.option(
151
+ "-db", "--database_name", required=False, type=str, help="DataBase name"
152
+ )
153
+ @click.option(
154
+ "-p",
155
+ "--path",
156
+ required=False,
157
+ type=str,
158
+ help="Path to the migrations directory",
159
+ )
160
+ @click.option(
161
+ "--allow-index-dropping/--forbid-index-dropping",
162
+ required=False,
163
+ default=False,
164
+ help="if allow-index-dropping is set, Beanie will drop indexes from your collection",
165
+ )
166
+ @click.option(
167
+ "--use-transaction/--no-use-transaction",
168
+ required=False,
169
+ default=True,
170
+ help="Enable or disable the use of transactions during migration. "
171
+ "When enabled (--use-transaction), Beanie uses transactions for migration, "
172
+ "which necessitates a replica set. When disabled (--no-use-transaction), "
173
+ "migrations occur without transactions.",
174
+ )
175
+ def migrate(
176
+ direction,
177
+ distance,
178
+ connection_uri,
179
+ database_name,
180
+ path,
181
+ allow_index_dropping,
182
+ use_transaction,
183
+ ):
184
+ settings_kwargs = {}
185
+ if direction:
186
+ settings_kwargs["direction"] = direction
187
+ if distance:
188
+ settings_kwargs["distance"] = distance
189
+ if connection_uri:
190
+ settings_kwargs["connection_uri"] = connection_uri
191
+ if database_name:
192
+ settings_kwargs["database_name"] = database_name
193
+ if path:
194
+ settings_kwargs["path"] = path
195
+ if allow_index_dropping:
196
+ settings_kwargs["allow_index_dropping"] = allow_index_dropping
197
+ settings_kwargs["use_transaction"] = use_transaction
198
+ settings = MigrationSettings(**settings_kwargs)
199
+
200
+ asyncio.run(run_migrate(settings))
201
+
202
+
203
+ @migrations.command()
204
+ @click.option("-n", "--name", required=True, type=str, help="Migration name")
205
+ @click.option(
206
+ "-p",
207
+ "--path",
208
+ required=True,
209
+ type=str,
210
+ help="Path to the migrations directory",
211
+ )
212
+ def new_migration(name, path):
213
+ path = Path(path)
214
+ ts_string = datetime.now().strftime("%Y%m%d%H%M%S")
215
+ file_name = f"{ts_string}_{name}.py"
216
+
217
+ shutil.copy(template.__file__, path / file_name)
218
+
219
+
220
+ if __name__ == "__main__":
221
+ migrations()
File without changes
File without changes
@@ -0,0 +1,18 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import List, Type
3
+
4
+ from beanis.odm.documents import Document
5
+
6
+
7
+ class BaseMigrationController(ABC):
8
+ def __init__(self, function):
9
+ self.function = function
10
+
11
+ @abstractmethod
12
+ async def run(self, session):
13
+ pass
14
+
15
+ @property
16
+ @abstractmethod
17
+ def models(self) -> List[Type[Document]]:
18
+ pass
@@ -0,0 +1,28 @@
1
+ from inspect import signature
2
+ from typing import List, Type
3
+
4
+ from beanis.migrations.controllers.base import BaseMigrationController
5
+ from beanis.odm.documents import Document
6
+
7
+
8
+ def free_fall_migration(document_models: List[Type[Document]]):
9
+ class FreeFallMigrationController(BaseMigrationController):
10
+ def __init__(self, function):
11
+ self.function = function
12
+ self.function_signature = signature(function)
13
+ self.document_models = document_models
14
+
15
+ def __call__(self, *args, **kwargs):
16
+ pass
17
+
18
+ @property
19
+ def models(self) -> List[Type[Document]]:
20
+ return self.document_models
21
+
22
+ async def run(self, session):
23
+ function_kwargs = {"session": session}
24
+ if "self" in self.function_signature.parameters:
25
+ function_kwargs["self"] = None
26
+ await self.function(**function_kwargs)
27
+
28
+ return FreeFallMigrationController
@@ -0,0 +1,130 @@
1
+ import asyncio
2
+ from inspect import isclass, signature
3
+ from typing import List, Optional, Type, Union
4
+
5
+ from beanis.migrations.controllers.base import BaseMigrationController
6
+ from beanis.migrations.utils import update_dict
7
+ from beanis.odm.documents import Document
8
+ from beanis.odm.utils.pydantic import parse_model
9
+
10
+
11
+ class DummyOutput:
12
+ def __init__(self):
13
+ super(DummyOutput, self).__setattr__("_internal_structure_dict", {})
14
+
15
+ def __setattr__(self, key, value):
16
+ self._internal_structure_dict[key] = value
17
+
18
+ def __getattr__(self, item):
19
+ try:
20
+ return self._internal_structure_dict[item]
21
+ except KeyError:
22
+ self._internal_structure_dict[item] = DummyOutput()
23
+ return self._internal_structure_dict[item]
24
+
25
+ def dict(self, to_parse: Optional[Union[dict, "DummyOutput"]] = None):
26
+ if to_parse is None:
27
+ to_parse = self
28
+ input_dict = (
29
+ to_parse._internal_structure_dict
30
+ if isinstance(to_parse, DummyOutput)
31
+ else to_parse
32
+ )
33
+ result_dict = {}
34
+ for key, value in input_dict.items():
35
+ if isinstance(value, (DummyOutput, dict)):
36
+ result_dict[key] = self.dict(to_parse=value)
37
+ else:
38
+ result_dict[key] = value
39
+ return result_dict
40
+
41
+
42
+ def iterative_migration(
43
+ document_models: Optional[List[Type[Document]]] = None,
44
+ batch_size: int = 10000,
45
+ ):
46
+ class IterativeMigration(BaseMigrationController):
47
+ def __init__(self, function):
48
+ self.function = function
49
+ self.function_signature = signature(function)
50
+ input_signature = self.function_signature.parameters.get(
51
+ "input_document"
52
+ )
53
+ if input_signature is None:
54
+ raise RuntimeError("input_signature must not be None")
55
+ self.input_document_model: Type[
56
+ Document
57
+ ] = input_signature.annotation
58
+ output_signature = self.function_signature.parameters.get(
59
+ "output_document"
60
+ )
61
+ if output_signature is None:
62
+ raise RuntimeError("output_signature must not be None")
63
+ self.output_document_model: Type[
64
+ Document
65
+ ] = output_signature.annotation
66
+
67
+ if (
68
+ not isclass(self.input_document_model)
69
+ or not issubclass(self.input_document_model, Document)
70
+ or not isclass(self.output_document_model)
71
+ or not issubclass(self.output_document_model, Document)
72
+ ):
73
+ raise TypeError(
74
+ "input_document and output_document "
75
+ "must have annotation of Document subclass"
76
+ )
77
+
78
+ self.batch_size = batch_size
79
+
80
+ def __call__(self, *args, **kwargs):
81
+ pass
82
+
83
+ @property
84
+ def models(self) -> List[Type[Document]]:
85
+ preset_models = document_models
86
+ if preset_models is None:
87
+ preset_models = []
88
+ return preset_models + [
89
+ self.input_document_model,
90
+ self.output_document_model,
91
+ ]
92
+
93
+ async def run(self, session):
94
+ output_documents = []
95
+ all_migration_ops = []
96
+ async for input_document in self.input_document_model.find_all(
97
+ session=session
98
+ ):
99
+ output = DummyOutput()
100
+ function_kwargs = {
101
+ "input_document": input_document,
102
+ "output_document": output,
103
+ }
104
+ if "self" in self.function_signature.parameters:
105
+ function_kwargs["self"] = None
106
+ await self.function(**function_kwargs)
107
+ output_dict = input_document.dict()
108
+ update_dict(output_dict, output.dict())
109
+ output_document = parse_model(
110
+ self.output_document_model, output_dict
111
+ )
112
+ output_documents.append(output_document)
113
+
114
+ if len(output_documents) == self.batch_size:
115
+ all_migration_ops.append(
116
+ self.output_document_model.replace_many(
117
+ documents=output_documents, session=session
118
+ )
119
+ )
120
+ output_documents = []
121
+
122
+ if output_documents:
123
+ all_migration_ops.append(
124
+ self.output_document_model.replace_many(
125
+ documents=output_documents, session=session
126
+ )
127
+ )
128
+ await asyncio.gather(*all_migration_ops)
129
+
130
+ return IterativeMigration
@@ -0,0 +1,16 @@
1
+ import motor.motor_asyncio
2
+
3
+
4
+ class DBHandler:
5
+ @classmethod
6
+ def set_db(cls, uri, db_name):
7
+ cls.client = motor.motor_asyncio.AsyncIOMotorClient(uri)
8
+ cls.database = cls.client[db_name]
9
+
10
+ @classmethod
11
+ def get_cli(cls):
12
+ return cls.client if hasattr(cls, "client") else None
13
+
14
+ @classmethod
15
+ def get_db(cls):
16
+ return cls.database if hasattr(cls, "database") else None
@@ -0,0 +1,33 @@
1
+ from datetime import datetime
2
+ from enum import Enum
3
+ from typing import List, Optional
4
+
5
+ from pydantic import Field
6
+ from pydantic.main import BaseModel
7
+
8
+ from beanis.odm.documents import Document
9
+
10
+
11
+ class MigrationLog(Document):
12
+ ts: datetime = Field(default_factory=datetime.now)
13
+ name: str
14
+ is_current: bool
15
+
16
+ class Settings:
17
+ name = "migrations_log"
18
+
19
+
20
+ class RunningDirections(str, Enum):
21
+ FORWARD = "FORWARD"
22
+ BACKWARD = "BACKWARD"
23
+
24
+
25
+ class RunningMode(BaseModel):
26
+ direction: RunningDirections
27
+ distance: int = 0
28
+
29
+
30
+ class ParsedMigrations(BaseModel):
31
+ path: str
32
+ names: List[str]
33
+ current: Optional[MigrationLog] = None