3import multiprocessing.pool
10import pymongo.collection
13from bson
import json_util
15from testsuite
import types, utils
17from .
import connection, ensure_db_indexes, mongo_schema, service
21DB_FILE_RE_PATTERN = re.compile(
r'^db_(?P<mongo_db_alias>\w+)\.json$')
22JSON_OPTIONS = json_util.JSONOptions(tz_aware=
False)
46 """Base testsuite error"""
49class UnknownCollectionError(BaseError):
53class CollectionWrapper:
54 def __init__(self, collections):
55 # TODO: deprecate collection as attribute
56 for alias, collection in collections.items():
57 setattr(self, alias, collection)
58 self._collections = collections.copy()
59 self._aliases = tuple(collections.keys())
61 def __getitem__(self, alias: str) -> pymongo.collection.Collection:
62 return self._collections[alias]
64 def __contains__(self, alias: str) -> bool:
65 return alias in self._collections
67 def get_aliases(self) -> tuple[str]:
71class CollectionWrapperFactory:
72 def __init__(self, connection_info: connection.ConnectionInfo):
73 self._connection_info = connection_info
76 def connection_string(self) -> str:
77 return self._connection_info.get_uri()
79 @utils.cached_property
80 def client(self) -> pymongo.MongoClient:
81 return pymongo.MongoClient(self.connection_string)
83 def create_collection_wrapper(
87 ) -> CollectionWrapper:
89 for name in collection_names:
90 if name not in mongodb_settings:
91 raise UnknownCollectionError(
92 f'Missing collection {name} in mongodb_settings fixture',
94 # pylint: disable=unsubscriptable-object
95 settings = mongodb_settings[name]['settings']
96 database = self.client[settings['database']]
97 collections[name] = database[settings['collection']]
98 return CollectionWrapper(collections)
101def pytest_configure(config):
102 config.addinivalue_line(
104 'noshuffledb: disable data set shuffle for marked test',
106 config.addinivalue_line(
108 'filldb: specify mongo static file suffix',
110 config.addinivalue_line(
112 'mongodb_collections: override mongo collections list',
116def pytest_addoption(parser):
118 :param parser: pytest's argument parser
120 group = parser.getgroup('mongo')
121 group.addoption('--mongo', help='Mongo connection string.')
125 help='Disable index creation.',
130 help='Disable fixture data shuffle.',
135 help='Disable collections sharding.',
139 help='Disable mongo startup',
143 'mongo-retry-writes',
147 "Controls value of 'retryWrites' parameter of mongo connection "
153def pytest_report_header(config):
154 conninfo = _get_connection_info(config)
155 return [f'Mongo: {conninfo.get_uri()}']
158def pytest_service_register(register_service):
159 register_service('mongo', service.create_mongo_service)
162def pytest_register_object_hooks():
163 return {key: _mongo_object_hook for key in MONGO_OBJECT_HOOKS}
169 _mongodb_local: CollectionWrapper,
170) -> CollectionWrapper:
171 """Returns MongoDB collections wrapper for the test.
173 Collections are initialized by the @c mongodb_init fixture.
175 @ingroup userver_testsuite_fixtures
176 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L167)
178 return _mongodb_local
182def mongo_connections(
184 mongo_connection_info,
185 mongo_extra_connections,
186 _mongo_local_collections,
188 mongo_connection_uri = mongo_connection_info.get_uri()
191 mongodb_settings[name]['settings'][
193 ]: mongo_connection_uri
194 for name in _mongo_local_collections
197 extra_conn: mongo_connection_uri
198 for extra_conn in mongo_extra_connections
204def mongo_extra_connections() -> tuple[str, ...]:
206 Override this if you need to access mongo connections besides those
207 defined in mongo_connections fixture
209 @ingroup userver_testsuite_fixtures
210 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L201)
215@pytest.fixture(scope='session')
216def mongo_connection_info(
218) -> connection.ConnectionInfo:
219 return _get_connection_info(pytestconfig)
224 mongo_schema_directory,
225 mongo_schema_extra_directories,
227) -> mongo_schema.MongoSchemas:
228 return mongo_schema.MongoSchemas(
230 (mongo_schema_directory, *mongo_schema_extra_directories),
235def mongodb_collections(mongodb_settings) -> tuple[str, ...]:
237 Override this to enable access to named collections within test module
239 Returns all available collections by default.
241 @ingroup userver_testsuite_fixtures
242 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L229)
244 return tuple(mongodb_settings.keys())
247@pytest.fixture(scope='session')
248def mongo_schema_extra_directories() -> tuple[str, ...]:
250 Override to use collection schemas besides those defined by
251 @c mongo_schema_directory fixture
253 @ingroup userver_testsuite_fixtures
254 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L239)
259@pytest.fixture(scope='session')
260def _mongo_indexes_ensured() -> set[str]:
267 ensure_service_started,
269 _mongo_service_settings,
271 aliases = _mongodb_local.get_aliases()
274 and not pytestconfig.option.mongo
275 and not pytestconfig.option.no_mongo
277 ensure_service_started('mongo', settings=_mongo_service_settings)
281def _mongo_create_indexes(
285 _mongo_indexes_ensured,
288 aliases = _mongodb_local.get_aliases()
289 if not pytestconfig.option.no_indexes:
291 for alias in aliases:
293 alias not in _mongo_indexes_ensured
294 and alias in mongodb_settings
296 _ensure_indexes[alias] = mongodb_settings[alias]
298 sharding_enabled = not pytestconfig.option.no_sharding
299 ensure_db_indexes.ensure_db_indexes(
302 sharding_enabled=sharding_enabled,
304 _mongo_indexes_ensured.update(_ensure_indexes)
307@pytest.fixture(scope='session')
308def _mongo_thread_pool() -> types.YieldFixture[
309 multiprocessing.pool.ThreadPool,
311 pool = multiprocessing.pool.ThreadPool(processes=1)
312 with contextlib.closing(pool):
317def _mongo_query_loader(load_json):
318 def loader(filename, missing_ok=False):
319 data = load_json(filename, missing_ok=missing_ok)
331 static_dir: pathlib.Path,
334 _mongo_create_indexes,
337 """Populate mongodb with fixture data.
339 @ingroup userver_testsuite_fixtures
340 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L316)
343 if request.node.get_closest_marker('nofilldb'):
346 # Disable shuffle to make some buggy test work
348 not request.config.option.no_shuffle_db
349 and not request.node.get_closest_marker('noshuffledb')
351 aliases = {key: key for key in _mongodb_local.get_aliases()}
354 for marker in request.node.iter_markers('filldb'):
355 for dbname, alias in marker.kwargs.items():
356 if dbname not in aliases:
357 raise UnknownCollectionError(
358 f'Unknown collection {dbname} requested'
360 if alias != 'default':
361 aliases[dbname] = '{}_{}'.format(dbname, alias)
362 requested.add(dbname)
364 def _verify_db_alias(file_path: pathlib.Path) -> bool:
365 if not _is_relevant_file(request, static_dir, file_path):
367 match = DB_FILE_RE_PATTERN.search(file_path.name)
369 db_alias = match.group('mongo_db_alias')
370 if db_alias not in aliases and not any(
371 db_alias.startswith(alias + '_') for alias in aliases
378 check_name='mongo_db_aliases',
379 text_at_fail='file has not valid mongo collection name alias '
380 '(probably should add to service.yaml)',
383 def load_collection(params):
384 dbname, alias = params
386 col = getattr(_mongodb_local, dbname)
387 except AttributeError:
390 docs = _mongo_query_loader(
391 f'db_{alias}.json', missing_ok=dbname not in requested
394 if not docs and col.find_one({}, []) is None:
398 # Make sure there is no tests that depend on order of
399 # documents in fixture file.
405 pymongo.DeleteMany({}),
406 *(pymongo.InsertOne(doc) for doc in docs),
410 except pymongo.errors.BulkWriteError as bwe:
411 pprint.pprint(bwe.details)
415 for dbname, alias in aliases.items():
416 pool_args.append((dbname, alias))
418 _mongo_thread_pool.map(load_collection, pool_args)
424 _mongo_local_collections,
425 _mongo_collection_wrapper_factory: CollectionWrapperFactory,
426) -> CollectionWrapper:
427 return _mongo_collection_wrapper_factory.create_collection_wrapper(
428 _mongo_local_collections,
433@pytest.fixture(scope='session')
434def _mongo_collection_wrapper_factory(
435 mongo_connection_info: connection.ConnectionInfo,
436) -> CollectionWrapperFactory:
437 return CollectionWrapperFactory(mongo_connection_info)
441def _mongo_local_collections(request, mongodb_collections) -> set[str]:
442 result = set(mongodb_collections)
443 for marker in request.node.iter_markers('mongodb_collections'):
444 result.update(marker.args)
448@pytest.fixture(scope='session')
449def _mongo_schema_cache() -> mongo_schema.MongoSchemaCache:
450 return mongo_schema.MongoSchemaCache()
453@pytest.fixture(scope='session')
454def _mongo_service_settings(
456) -> service.ServiceSettings | None:
457 if pytestconfig.option.mongo:
459 return service.get_service_settings()
462def _is_relevant_file(
464 static_dir: pathlib.Path,
465 file_path: pathlib.Path,
467 default_static_dir = static_dir / 'default'
468 module_static_dir = static_dir / pathlib.Path(request.fspath).stem
469 return _is_nested_path(file_path, default_static_dir) or _is_nested_path(
475def _is_nested_path(parent: pathlib.Path, nested: pathlib.Path) -> bool:
477 pathlib.PurePath(nested).relative_to(parent)
483def _mongo_object_hook(doc):
484 return json_util.object_hook(doc, JSON_OPTIONS)
487def _get_connection_info(config):
488 # External mongo instance
489 if config.option.mongo:
490 return connection.parse_connection_uri(config.option.mongo)
491 service_settings = service.get_service_settings()
492 connection_info = service_settings.get_connection_info()
493 retry_writes = config.getini('mongo-retry-writes')
494 return dataclasses.replace(connection_info, retry_writes=retry_writes)