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 'nofilldb: disable MongoDB fixture-data loading for marked test',
106 config.addinivalue_line(
108 'noshuffledb: disable data set shuffle for marked test',
110 config.addinivalue_line(
112 'filldb: specify mongo static file suffix',
114 config.addinivalue_line(
116 'mongodb_collections: override mongo collections list',
120def pytest_addoption(parser):
122 :param parser: pytest's argument parser
124 group = parser.getgroup('mongo')
125 group.addoption('--mongo', help='Mongo connection string.')
129 help='Disable index creation.',
134 help='Disable fixture data shuffle.',
139 help='Disable collections sharding.',
143 help='Disable mongo startup',
147 'mongo-retry-writes',
151 "Controls value of 'retryWrites' parameter of mongo connection "
157def pytest_report_header(config):
158 conninfo = _get_connection_info(config)
159 return [f'Mongo: {conninfo.get_uri()}']
162def pytest_service_register(register_service):
163 register_service('mongo', service.create_mongo_service)
166def pytest_register_object_hooks():
167 return {key: _mongo_object_hook for key in MONGO_OBJECT_HOOKS}
173 _mongodb_local: CollectionWrapper,
174) -> CollectionWrapper:
175 """Returns MongoDB collections wrapper for the test.
177 Collections are initialized by the @c mongodb_init fixture.
179 @ingroup userver_testsuite_fixtures
180 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L171)
182 return _mongodb_local
186def mongo_connections(
188 mongo_connection_info,
189 mongo_extra_connections,
190 _mongo_local_collections,
192 mongo_connection_uri = mongo_connection_info.get_uri()
195 mongodb_settings[name]['settings'][
197 ]: mongo_connection_uri
198 for name in _mongo_local_collections
201 extra_conn: mongo_connection_uri
202 for extra_conn in mongo_extra_connections
208def mongo_extra_connections() -> tuple[str, ...]:
210 Override this if you need to access mongo connections besides those
211 defined in mongo_connections fixture
213 @ingroup userver_testsuite_fixtures
214 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L205)
219@pytest.fixture(scope='session')
220def mongo_connection_info(
222) -> connection.ConnectionInfo:
223 return _get_connection_info(pytestconfig)
228 mongo_schema_directory,
229 mongo_schema_extra_directories,
231) -> mongo_schema.MongoSchemas:
232 return mongo_schema.MongoSchemas(
234 (mongo_schema_directory, *mongo_schema_extra_directories),
239def mongodb_collections(mongodb_settings) -> tuple[str, ...]:
241 Override this to enable access to named collections within test module
243 Returns all available collections by default.
245 @ingroup userver_testsuite_fixtures
246 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L233)
248 return tuple(mongodb_settings.keys())
251@pytest.fixture(scope='session')
252def mongo_schema_extra_directories() -> tuple[str, ...]:
254 Override to use collection schemas besides those defined by
255 @c mongo_schema_directory fixture
257 @ingroup userver_testsuite_fixtures
258 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L243)
263@pytest.fixture(scope='session')
264def _mongo_indexes_ensured() -> set[str]:
271 ensure_service_started,
273 _mongo_service_settings,
275 aliases = _mongodb_local.get_aliases()
278 and not pytestconfig.option.mongo
279 and not pytestconfig.option.no_mongo
281 ensure_service_started('mongo', settings=_mongo_service_settings)
285def _mongo_create_indexes(
289 _mongo_indexes_ensured,
292 aliases = _mongodb_local.get_aliases()
293 if not pytestconfig.option.no_indexes:
295 for alias in aliases:
297 alias not in _mongo_indexes_ensured
298 and alias in mongodb_settings
300 _ensure_indexes[alias] = mongodb_settings[alias]
302 sharding_enabled = not pytestconfig.option.no_sharding
303 ensure_db_indexes.ensure_db_indexes(
306 sharding_enabled=sharding_enabled,
308 _mongo_indexes_ensured.update(_ensure_indexes)
311@pytest.fixture(scope='session')
312def _mongo_thread_pool() -> types.YieldFixture[
313 multiprocessing.pool.ThreadPool,
315 pool = multiprocessing.pool.ThreadPool(processes=1)
316 with contextlib.closing(pool):
321def _mongo_query_loader(load_json):
322 def loader(filename, missing_ok=False):
323 data = load_json(filename, missing_ok=missing_ok)
335 static_dir: pathlib.Path,
338 _mongo_create_indexes,
341 """Populate mongodb with fixture data.
343 @ingroup userver_testsuite_fixtures
344 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/mongo/pytest_plugin.py#L320)
347 if request.node.get_closest_marker('nofilldb'):
350 # Disable shuffle to make some buggy test work
352 not request.config.option.no_shuffle_db
353 and not request.node.get_closest_marker('noshuffledb')
355 aliases = {key: key for key in _mongodb_local.get_aliases()}
358 for marker in request.node.iter_markers('filldb'):
359 for dbname, alias in marker.kwargs.items():
360 if dbname not in aliases:
361 raise UnknownCollectionError(
362 f'Unknown collection {dbname} requested'
364 if alias != 'default':
365 aliases[dbname] = '{}_{}'.format(dbname, alias)
366 requested.add(dbname)
368 def _verify_db_alias(file_path: pathlib.Path) -> bool:
369 if not _is_relevant_file(request, static_dir, file_path):
371 match = DB_FILE_RE_PATTERN.search(file_path.name)
373 db_alias = match.group('mongo_db_alias')
374 if db_alias not in aliases and not any(
375 db_alias.startswith(alias + '_') for alias in aliases
382 check_name='mongo_db_aliases',
383 text_at_fail='file has not valid mongo collection name alias '
384 '(probably should add to service.yaml)',
387 def load_collection(params):
388 dbname, alias = params
390 col = getattr(_mongodb_local, dbname)
391 except AttributeError:
394 docs = _mongo_query_loader(
395 f'db_{alias}.json', missing_ok=dbname not in requested
398 if not docs and col.find_one({}, []) is None:
402 # Make sure there is no tests that depend on order of
403 # documents in fixture file.
409 pymongo.DeleteMany({}),
410 *(pymongo.InsertOne(doc) for doc in docs),
414 except pymongo.errors.BulkWriteError as bwe:
415 pprint.pprint(bwe.details)
419 for dbname, alias in aliases.items():
420 pool_args.append((dbname, alias))
422 _mongo_thread_pool.map(load_collection, pool_args)
428 _mongo_local_collections,
429 _mongo_collection_wrapper_factory: CollectionWrapperFactory,
430) -> CollectionWrapper:
431 return _mongo_collection_wrapper_factory.create_collection_wrapper(
432 _mongo_local_collections,
437@pytest.fixture(scope='session')
438def _mongo_collection_wrapper_factory(
439 mongo_connection_info: connection.ConnectionInfo,
440) -> CollectionWrapperFactory:
441 return CollectionWrapperFactory(mongo_connection_info)
445def _mongo_local_collections(request, mongodb_collections) -> set[str]:
446 result = set(mongodb_collections)
447 for marker in request.node.iter_markers('mongodb_collections'):
448 result.update(marker.args)
452@pytest.fixture(scope='session')
453def _mongo_schema_cache() -> mongo_schema.MongoSchemaCache:
454 return mongo_schema.MongoSchemaCache()
457@pytest.fixture(scope='session')
458def _mongo_service_settings(
460) -> service.ServiceSettings | None:
461 if pytestconfig.option.mongo:
463 return service.get_service_settings()
466def _is_relevant_file(
468 static_dir: pathlib.Path,
469 file_path: pathlib.Path,
471 default_static_dir = static_dir / 'default'
472 module_static_dir = static_dir / pathlib.Path(request.fspath).stem
473 return _is_nested_path(file_path, default_static_dir) or _is_nested_path(
479def _is_nested_path(parent: pathlib.Path, nested: pathlib.Path) -> bool:
481 pathlib.PurePath(nested).relative_to(parent)
487def _mongo_object_hook(doc):
488 return json_util.object_hook(doc, JSON_OPTIONS)
491def _get_connection_info(config):
492 # External mongo instance
493 if config.option.mongo:
494 return connection.parse_connection_uri(config.option.mongo)
495 service_settings = service.get_service_settings()
496 connection_info = service_settings.get_connection_info()
497 retry_writes = config.getini('mongo-retry-writes')
498 return dataclasses.replace(connection_info, retry_writes=retry_writes)