atlas-init 0.4.4__py3-none-any.whl → 0.6.0__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.
- atlas_init/__init__.py +1 -1
- atlas_init/cli.py +2 -0
- atlas_init/cli_cfn/app.py +3 -4
- atlas_init/cli_cfn/cfn_parameter_finder.py +61 -53
- atlas_init/cli_cfn/contract.py +4 -7
- atlas_init/cli_cfn/example.py +8 -18
- atlas_init/cli_helper/go.py +7 -11
- atlas_init/cli_root/mms_released.py +46 -0
- atlas_init/cli_root/trigger.py +6 -6
- atlas_init/cli_tf/app.py +3 -84
- atlas_init/cli_tf/ci_tests.py +493 -0
- atlas_init/cli_tf/codegen/__init__.py +0 -0
- atlas_init/cli_tf/codegen/models.py +97 -0
- atlas_init/cli_tf/codegen/openapi_minimal.py +74 -0
- atlas_init/cli_tf/github_logs.py +7 -94
- atlas_init/cli_tf/go_test_run.py +385 -132
- atlas_init/cli_tf/go_test_summary.py +331 -4
- atlas_init/cli_tf/go_test_tf_error.py +380 -0
- atlas_init/cli_tf/hcl/modifier.py +14 -12
- atlas_init/cli_tf/hcl/modifier2.py +87 -0
- atlas_init/cli_tf/mock_tf_log.py +1 -1
- atlas_init/cli_tf/{schema_v2_api_parsing.py → openapi.py} +95 -17
- atlas_init/cli_tf/schema_v2.py +43 -1
- atlas_init/crud/__init__.py +0 -0
- atlas_init/crud/mongo_client.py +115 -0
- atlas_init/crud/mongo_dao.py +296 -0
- atlas_init/crud/mongo_utils.py +239 -0
- atlas_init/repos/go_sdk.py +12 -3
- atlas_init/repos/path.py +110 -7
- atlas_init/settings/config.py +3 -6
- atlas_init/settings/env_vars.py +22 -31
- atlas_init/settings/interactive2.py +134 -0
- atlas_init/tf/.terraform.lock.hcl +59 -59
- atlas_init/tf/always.tf +5 -5
- atlas_init/tf/main.tf +3 -3
- atlas_init/tf/modules/aws_kms/aws_kms.tf +1 -1
- atlas_init/tf/modules/aws_s3/provider.tf +2 -1
- atlas_init/tf/modules/aws_vpc/provider.tf +2 -1
- atlas_init/tf/modules/cfn/cfn.tf +0 -8
- atlas_init/tf/modules/cfn/kms.tf +5 -5
- atlas_init/tf/modules/cfn/provider.tf +7 -0
- atlas_init/tf/modules/cfn/variables.tf +1 -1
- atlas_init/tf/modules/cloud_provider/cloud_provider.tf +1 -1
- atlas_init/tf/modules/cloud_provider/provider.tf +2 -1
- atlas_init/tf/modules/cluster/cluster.tf +31 -31
- atlas_init/tf/modules/cluster/provider.tf +2 -1
- atlas_init/tf/modules/encryption_at_rest/provider.tf +2 -1
- atlas_init/tf/modules/federated_vars/federated_vars.tf +1 -1
- atlas_init/tf/modules/federated_vars/provider.tf +2 -1
- atlas_init/tf/modules/project_extra/project_extra.tf +1 -10
- atlas_init/tf/modules/project_extra/provider.tf +8 -0
- atlas_init/tf/modules/stream_instance/provider.tf +8 -0
- atlas_init/tf/modules/stream_instance/stream_instance.tf +0 -9
- atlas_init/tf/modules/vpc_peering/provider.tf +10 -0
- atlas_init/tf/modules/vpc_peering/vpc_peering.tf +0 -10
- atlas_init/tf/modules/vpc_privatelink/versions.tf +2 -1
- atlas_init/tf/outputs.tf +1 -0
- atlas_init/tf/providers.tf +1 -1
- atlas_init/tf/variables.tf +7 -7
- atlas_init/typer_app.py +4 -8
- {atlas_init-0.4.4.dist-info → atlas_init-0.6.0.dist-info}/METADATA +7 -4
- atlas_init-0.6.0.dist-info/RECORD +121 -0
- atlas_init-0.4.4.dist-info/RECORD +0 -105
- {atlas_init-0.4.4.dist-info → atlas_init-0.6.0.dist-info}/WHEEL +0 -0
- {atlas_init-0.4.4.dist-info → atlas_init-0.6.0.dist-info}/entry_points.txt +0 -0
- {atlas_init-0.4.4.dist-info → atlas_init-0.6.0.dist-info}/licenses/LICENSE +0 -0
@@ -5,7 +5,7 @@ import re
|
|
5
5
|
from collections.abc import Iterable
|
6
6
|
from pathlib import Path
|
7
7
|
from queue import Queue
|
8
|
-
from typing import ClassVar
|
8
|
+
from typing import ClassVar, NamedTuple
|
9
9
|
|
10
10
|
from model_lib import Entity, dump
|
11
11
|
from pydantic import Field
|
@@ -48,6 +48,21 @@ def parse_openapi_schema_after_modifications(schema: SchemaV2, api_spec_path: Pa
|
|
48
48
|
return api_spec_text_changes(schema, original)
|
49
49
|
|
50
50
|
|
51
|
+
class PathMethodCode(NamedTuple):
|
52
|
+
path: str
|
53
|
+
method: str
|
54
|
+
code: str
|
55
|
+
|
56
|
+
|
57
|
+
def extract_api_version_content_header(header: str) -> str | None:
|
58
|
+
"""
|
59
|
+
Extracts the API version from the content header.
|
60
|
+
The header should be in the format 'application/vnd.atlas.v1+json'.
|
61
|
+
"""
|
62
|
+
match = re.match(r"application/vnd\.atlas\.v?(?P<version>[\d-]+)\+json", header)
|
63
|
+
return match.group("version") if match else None
|
64
|
+
|
65
|
+
|
51
66
|
class OpenapiSchema(Entity):
|
52
67
|
PARAMETERS_PREFIX: ClassVar[str] = "#/components/parameters/"
|
53
68
|
SCHEMAS_PREFIX: ClassVar[str] = "#/components/schemas/"
|
@@ -64,13 +79,32 @@ class OpenapiSchema(Entity):
|
|
64
79
|
def read_method(self, path: str) -> dict | None:
|
65
80
|
return self.paths.get(path, {}).get("get")
|
66
81
|
|
82
|
+
def delete_method(self, path: str) -> dict | None:
|
83
|
+
return self.paths.get(path, {}).get("delete")
|
84
|
+
|
85
|
+
def patch_method(self, path: str) -> dict | None:
|
86
|
+
return self.paths.get(path, {}).get("patch")
|
87
|
+
|
88
|
+
def put_method(self, path: str) -> dict | None:
|
89
|
+
return self.paths.get(path, {}).get("patch")
|
90
|
+
|
91
|
+
def methods(self, path: str) -> Iterable[dict]:
|
92
|
+
for method_name in ["post", "get", "delete", "patch", "put"]:
|
93
|
+
if method := self.paths.get(path, {}).get(method_name):
|
94
|
+
yield method
|
95
|
+
|
67
96
|
def method_refs(self, path: str) -> Iterable[str]:
|
68
|
-
for method in
|
97
|
+
for method in self.methods(path):
|
69
98
|
if method:
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
99
|
+
yield from self.method_request_body_ref(method)
|
100
|
+
yield from self.method_response_ref(method)
|
101
|
+
|
102
|
+
def parameter_refs(self, path: str) -> Iterable[str]:
|
103
|
+
for method in self.methods(path):
|
104
|
+
parameters = method.get("parameters", [])
|
105
|
+
for param in parameters:
|
106
|
+
if param_ref := param.get("$ref"):
|
107
|
+
yield param_ref
|
74
108
|
|
75
109
|
def parameter(self, ref: str) -> dict:
|
76
110
|
assert ref.startswith(OpenapiSchema.PARAMETERS_PREFIX)
|
@@ -91,23 +125,50 @@ class OpenapiSchema(Entity):
|
|
91
125
|
prop["name"] = name
|
92
126
|
yield prop
|
93
127
|
|
94
|
-
def method_request_body_ref(self, method: dict) -> str
|
128
|
+
def method_request_body_ref(self, method: dict) -> Iterable[str]:
|
95
129
|
request_body = method.get("requestBody", {})
|
96
|
-
|
130
|
+
yield from self._unpack_schema_ref(request_body)
|
97
131
|
|
98
|
-
def method_response_ref(self, method: dict) -> str
|
132
|
+
def method_response_ref(self, method: dict) -> Iterable[str]:
|
99
133
|
responses = method.get("responses", {})
|
100
134
|
ok_response = responses.get("200", {})
|
101
|
-
|
135
|
+
yield from self._unpack_schema_ref(ok_response)
|
102
136
|
|
103
|
-
def _unpack_schema_ref(self, response: dict) -> str
|
137
|
+
def _unpack_schema_ref(self, response: dict) -> Iterable[str]:
|
104
138
|
content = {**response.get("content", {})} # avoid side effects
|
105
139
|
if not content:
|
106
140
|
return None
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
141
|
+
while content:
|
142
|
+
key, value = content.popitem()
|
143
|
+
if not isinstance(key, str) or not key.endswith("json"):
|
144
|
+
continue
|
145
|
+
if ref := value.get("schema", {}).get("$ref"):
|
146
|
+
yield ref
|
147
|
+
|
148
|
+
def _unpack_schema_versions(self, response: dict) -> list[str]:
|
149
|
+
content: dict[str, dict] = {**response.get("content", {})}
|
150
|
+
versions = []
|
151
|
+
while content:
|
152
|
+
key, value = content.popitem()
|
153
|
+
if not isinstance(value, dict) or not key.endswith("json"):
|
154
|
+
continue
|
155
|
+
if version := value.get("x-xgen-version"):
|
156
|
+
versions.append(version)
|
157
|
+
continue
|
158
|
+
if version := extract_api_version_content_header(key):
|
159
|
+
versions.append(version)
|
160
|
+
return versions
|
161
|
+
|
162
|
+
def path_method_api_versions(self) -> Iterable[tuple[PathMethodCode, list[str]]]:
|
163
|
+
for path, methods in self.paths.items():
|
164
|
+
for method_name, method_dict in methods.items():
|
165
|
+
if not isinstance(method_dict, dict):
|
166
|
+
continue
|
167
|
+
responses = method_dict.get("responses", {})
|
168
|
+
for code, response_dict in responses.items():
|
169
|
+
if api_versions := self._unpack_schema_versions(response_dict):
|
170
|
+
key = PathMethodCode(path, method_name, code)
|
171
|
+
yield key, api_versions
|
111
172
|
|
112
173
|
def schema_ref_component(self, ref: str, attributes_skip: set[str]) -> SchemaResource:
|
113
174
|
schemas = self.components.get("schemas", {})
|
@@ -128,6 +189,9 @@ class OpenapiSchema(Entity):
|
|
128
189
|
name=ref,
|
129
190
|
description=schema.get("description", ""),
|
130
191
|
attributes_skip=attributes_skip,
|
192
|
+
discriminator=schema.get("discriminator"),
|
193
|
+
one_of=schema.get("oneOf", []),
|
194
|
+
all_of=schema.get("allOf", []),
|
131
195
|
)
|
132
196
|
required_names = schema.get("required", [])
|
133
197
|
for prop in self.schema_properties(ref):
|
@@ -143,6 +207,16 @@ class OpenapiSchema(Entity):
|
|
143
207
|
elif ref.startswith(self.SCHEMAS_PREFIX):
|
144
208
|
prefix = self.SCHEMAS_PREFIX
|
145
209
|
parent_dict = self.components["schemas"]
|
210
|
+
ref_value.pop("name", None)
|
211
|
+
if properties := ref_value.get("properties"):
|
212
|
+
properties_no_name = {
|
213
|
+
k: {nested_k: nested_v for nested_k, nested_v in v.items() if nested_k != "name"}
|
214
|
+
for k, v in properties.items()
|
215
|
+
}
|
216
|
+
if ref.removeprefix(prefix).endswith("DBRoleToExecute"):
|
217
|
+
logger.warning(f"debug me: {properties_no_name}")
|
218
|
+
ref_value["properties"] = properties_no_name
|
219
|
+
|
146
220
|
else:
|
147
221
|
err_msg = f"Unknown schema_ref {ref}"
|
148
222
|
raise ValueError(err_msg)
|
@@ -168,12 +242,14 @@ def parse_api_spec_param(api_spec: OpenapiSchema, param: dict, resource: SchemaR
|
|
168
242
|
case {"$ref": ref, "name": name} if ref.startswith(OpenapiSchema.SCHEMAS_PREFIX):
|
169
243
|
# nested attribute
|
170
244
|
attribute = SchemaAttribute(
|
245
|
+
additional_properties=param.get("additionalProperties", {}),
|
171
246
|
type="object",
|
172
247
|
name=name,
|
173
248
|
schema_ref=ref,
|
174
249
|
)
|
175
250
|
case {"type": "array", "items": {"$ref": ref}, "name": name}:
|
176
251
|
attribute = SchemaAttribute(
|
252
|
+
additional_properties=param.get("additionalProperties", {}),
|
177
253
|
type="array",
|
178
254
|
name=name,
|
179
255
|
schema_ref=ref,
|
@@ -183,6 +259,7 @@ def parse_api_spec_param(api_spec: OpenapiSchema, param: dict, resource: SchemaR
|
|
183
259
|
)
|
184
260
|
case {"name": name, "schema": schema}:
|
185
261
|
attribute = SchemaAttribute(
|
262
|
+
additional_properties=param.get("additionalProperties", {}),
|
186
263
|
type=schema["type"],
|
187
264
|
name=name,
|
188
265
|
description=param.get("description", ""),
|
@@ -196,6 +273,7 @@ def parse_api_spec_param(api_spec: OpenapiSchema, param: dict, resource: SchemaR
|
|
196
273
|
description=param.get("description", ""),
|
197
274
|
is_computed=param.get("readOnly", False),
|
198
275
|
is_required=param.get("required", False),
|
276
|
+
additional_properties=param.get("additionalProperties", {}),
|
199
277
|
)
|
200
278
|
case _:
|
201
279
|
raise NotImplementedError
|
@@ -220,7 +298,7 @@ def add_api_spec_info(schema: SchemaV2, api_spec_path: Path, *, minimal_refs: bo
|
|
220
298
|
continue
|
221
299
|
for param in create_method.get("parameters", []):
|
222
300
|
parse_api_spec_param(api_spec, param, resource)
|
223
|
-
|
301
|
+
for req_ref in api_spec.method_request_body_ref(create_method):
|
224
302
|
for property_dict in api_spec.schema_properties(req_ref):
|
225
303
|
parse_api_spec_param(api_spec, property_dict, resource)
|
226
304
|
for path in resource.paths:
|
@@ -229,7 +307,7 @@ def add_api_spec_info(schema: SchemaV2, api_spec_path: Path, *, minimal_refs: bo
|
|
229
307
|
continue
|
230
308
|
for param in read_method.get("parameters", []):
|
231
309
|
parse_api_spec_param(api_spec, param, resource)
|
232
|
-
|
310
|
+
for response_ref in api_spec.method_response_ref(read_method):
|
233
311
|
for property_dict in api_spec.schema_properties(response_ref):
|
234
312
|
parse_api_spec_param(api_spec, property_dict, resource)
|
235
313
|
if minimal_refs:
|
atlas_init/cli_tf/schema_v2.py
CHANGED
@@ -8,11 +8,12 @@ from fnmatch import fnmatch
|
|
8
8
|
from pathlib import Path
|
9
9
|
from queue import Queue
|
10
10
|
from tempfile import TemporaryDirectory
|
11
|
-
from typing import Literal, TypeAlias
|
11
|
+
from typing import Any, Literal, TypeAlias
|
12
12
|
|
13
13
|
from model_lib import Entity, copy_and_validate, parse_model
|
14
14
|
from pydantic import ConfigDict, Field, model_validator
|
15
15
|
from zero_3rdparty.enum_utils import StrEnum
|
16
|
+
from zero_3rdparty.iter_utils import flat_map
|
16
17
|
|
17
18
|
from atlas_init.cli_helper.run import run_binary_command_is_ok
|
18
19
|
from atlas_init.humps import decamelize, pascalize
|
@@ -50,6 +51,13 @@ class SchemaAttribute(Entity):
|
|
50
51
|
validators: list[SchemaAttributeValidator] = Field(default_factory=list)
|
51
52
|
# not used during dumping but backtrace which parameters are used in the api spec
|
52
53
|
parameter_ref: str = ""
|
54
|
+
additional_properties: dict[str, Any] = Field(default_factory=dict)
|
55
|
+
|
56
|
+
@property
|
57
|
+
def additional_properties_ref(self) -> str:
|
58
|
+
if props := self.additional_properties:
|
59
|
+
return props.get("$ref", "")
|
60
|
+
return ""
|
53
61
|
|
54
62
|
@property
|
55
63
|
def tf_name(self) -> str:
|
@@ -88,6 +96,7 @@ class SchemaAttribute(Entity):
|
|
88
96
|
plan_modifiers=self.plan_modifiers + other.plan_modifiers,
|
89
97
|
validators=self.validators + other.validators,
|
90
98
|
parameter_ref=self.parameter_ref or other.parameter_ref,
|
99
|
+
additional_properties=self.additional_properties | other.additional_properties,
|
91
100
|
)
|
92
101
|
|
93
102
|
def set_attribute_type(
|
@@ -175,6 +184,29 @@ class SDKConversion(Entity):
|
|
175
184
|
return bool(self.sdk_start_refs)
|
176
185
|
|
177
186
|
|
187
|
+
class Discriminator(Entity):
|
188
|
+
mapping: dict[str, str] = Field(default_factory=dict)
|
189
|
+
property_name: str = Field(alias="propertyName")
|
190
|
+
|
191
|
+
|
192
|
+
class OneOf(Entity):
|
193
|
+
ref: str = Field(alias="$ref", default="")
|
194
|
+
|
195
|
+
|
196
|
+
class AllOf(Entity):
|
197
|
+
ref: str = Field(alias="$ref", default="")
|
198
|
+
properties: dict[str, Any] = Field(default_factory=dict)
|
199
|
+
|
200
|
+
@property
|
201
|
+
def nested_refs(self) -> set[str]:
|
202
|
+
refs = set()
|
203
|
+
for prop, prop_value in self.properties.items():
|
204
|
+
if isinstance(prop_value, dict):
|
205
|
+
if ref := prop_value.get("$ref"):
|
206
|
+
refs.add(ref)
|
207
|
+
return refs
|
208
|
+
|
209
|
+
|
178
210
|
class SchemaResource(Entity):
|
179
211
|
name: str = "" # populated by the key of the resources dict
|
180
212
|
description: str = ""
|
@@ -183,6 +215,16 @@ class SchemaResource(Entity):
|
|
183
215
|
paths: list[str] = Field(default_factory=list)
|
184
216
|
attribute_type_modifiers: AttributeTypeModifiers = Field(default_factory=AttributeTypeModifiers)
|
185
217
|
conversion: SDKConversion = Field(default_factory=SDKConversion)
|
218
|
+
discriminator: Discriminator | None = None
|
219
|
+
one_of: list[OneOf] = Field(default_factory=list)
|
220
|
+
all_of: list[AllOf] = Field(default_factory=list)
|
221
|
+
|
222
|
+
def extra_refs(self) -> set[str]:
|
223
|
+
return (
|
224
|
+
{one_of.ref for one_of in self.one_of if one_of.ref}
|
225
|
+
| {all_of.ref for all_of in self.all_of if all_of.ref}
|
226
|
+
| {ref for ref in flat_map(all_of.nested_refs for all_of in self.all_of) if ref}
|
227
|
+
)
|
186
228
|
|
187
229
|
@model_validator(mode="after")
|
188
230
|
def set_attribute_names(self):
|
File without changes
|
@@ -0,0 +1,115 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from dataclasses import dataclass, field
|
4
|
+
import logging
|
5
|
+
from typing import TypeAlias
|
6
|
+
|
7
|
+
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase, AsyncIOMotorCollection
|
8
|
+
from pymongo import IndexModel
|
9
|
+
from pymongo.errors import DuplicateKeyError
|
10
|
+
from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed
|
11
|
+
|
12
|
+
from atlas_init.cli_tf.go_test_run import GoTestRun
|
13
|
+
from atlas_init.cli_tf.go_test_tf_error import GoTestErrorClassification
|
14
|
+
from atlas_init.crud.mongo_utils import index_dec
|
15
|
+
|
16
|
+
|
17
|
+
logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass
|
21
|
+
class CollectionConfig:
|
22
|
+
name: str = "" # uses the class name by default
|
23
|
+
indexes: list[IndexModel] = field(default_factory=list)
|
24
|
+
|
25
|
+
|
26
|
+
CollectionConfigsT: TypeAlias = dict[type, CollectionConfig]
|
27
|
+
|
28
|
+
|
29
|
+
def default_document_models() -> CollectionConfigsT:
|
30
|
+
return {
|
31
|
+
GoTestErrorClassification: CollectionConfig(
|
32
|
+
indexes=[index_dec("ts"), IndexModel(["error_class"]), IndexModel(["test_name"])]
|
33
|
+
),
|
34
|
+
GoTestRun: CollectionConfig(indexes=[index_dec("ts"), IndexModel(["branch"]), IndexModel(["status"])]),
|
35
|
+
}
|
36
|
+
|
37
|
+
|
38
|
+
_collections = {}
|
39
|
+
|
40
|
+
|
41
|
+
def get_collection(model: type) -> AsyncIOMotorCollection:
|
42
|
+
col = _collections.get(model)
|
43
|
+
if col is not None:
|
44
|
+
return col
|
45
|
+
raise ValueError(f"Collection for model {model.__name__} is not initialized. Call init_mongo first.")
|
46
|
+
|
47
|
+
|
48
|
+
def get_db(mongo_url: str, db_name: str) -> AsyncIOMotorDatabase:
|
49
|
+
client = AsyncIOMotorClient(mongo_url)
|
50
|
+
return client.get_database(db_name)
|
51
|
+
|
52
|
+
|
53
|
+
async def init_mongo(
|
54
|
+
mongo_url: str, db_name: str, clean_collections: bool = False, document_models: CollectionConfigsT | None = None
|
55
|
+
) -> None:
|
56
|
+
db = get_db(mongo_url, db_name)
|
57
|
+
document_models = document_models or default_document_models()
|
58
|
+
for model, cfg in document_models.items():
|
59
|
+
name = cfg.name or model.__name__
|
60
|
+
col = await ensure_collection_exist(db, name, cfg.indexes, clean_collections)
|
61
|
+
_collections[model] = col
|
62
|
+
|
63
|
+
if clean_collections:
|
64
|
+
logger.info(f"MongoDB collections in '{db_name}' have been cleaned.")
|
65
|
+
|
66
|
+
|
67
|
+
async def ensure_collection_exist(
|
68
|
+
db: AsyncIOMotorDatabase,
|
69
|
+
name: str,
|
70
|
+
indexes: list[IndexModel] | None = None,
|
71
|
+
clean_collection: bool = False,
|
72
|
+
) -> AsyncIOMotorCollection:
|
73
|
+
existing = await db.list_collection_names()
|
74
|
+
if clean_collection and name in existing:
|
75
|
+
await db.drop_collection(name)
|
76
|
+
existing.remove(name)
|
77
|
+
|
78
|
+
if name not in existing:
|
79
|
+
await db.create_collection(name)
|
80
|
+
|
81
|
+
if indexes:
|
82
|
+
# always (re-)create indexes after new creation or drop
|
83
|
+
await db[name].create_indexes(indexes)
|
84
|
+
|
85
|
+
logger.debug(f"mongo collection {name!r} is ready")
|
86
|
+
return db[name]
|
87
|
+
|
88
|
+
|
89
|
+
def duplicate_key_pattern(error: DuplicateKeyError) -> str | None:
|
90
|
+
details: dict = error.details # type: ignore
|
91
|
+
name_violator = details.get("keyPattern", {})
|
92
|
+
if not name_violator:
|
93
|
+
return None
|
94
|
+
name, _ = name_violator.popitem()
|
95
|
+
return name
|
96
|
+
|
97
|
+
|
98
|
+
class CollectionNotEmptyError(Exception):
|
99
|
+
def __init__(self, collection_name: str):
|
100
|
+
super().__init__(f"Collection '{collection_name}' is not empty.")
|
101
|
+
self.collection_name = collection_name
|
102
|
+
|
103
|
+
|
104
|
+
@retry(
|
105
|
+
stop=stop_after_attempt(10),
|
106
|
+
wait=wait_fixed(0.5),
|
107
|
+
retry=retry_if_exception_type(CollectionNotEmptyError),
|
108
|
+
reraise=True,
|
109
|
+
)
|
110
|
+
async def _empty_collections() -> None:
|
111
|
+
col: AsyncIOMotorCollection
|
112
|
+
for col in _collections.values():
|
113
|
+
count = await col.count_documents({})
|
114
|
+
if count > 0:
|
115
|
+
raise CollectionNotEmptyError(col.name)
|