3import concurrent.futures
10from .
import connection, control, discover, exceptions, service, utils
12DB_FILE_RE_PATTERN = re.compile(
r'/pg_(?P<pg_db_alias>\w+)(/?\w*)\.sql$')
20 cleanup_exclude_tables: frozenset[str],
26 shard.pretty_name: pgsql_control.get_connection_cached(
30 for shard
in db.shards
34 def __len__(self) -> int:
37 def __iter__(self) -> typing.Iterator[str]:
42 :py:class:`testsuite.databases.pgsql.connection.PgConnectionInfo`
43 instance by database name
48 self, parallel_init: bool
56 def init_database(db):
59 for shard
in db.shards:
65 with concurrent.futures.ThreadPoolExecutor()
as executor:
67 executor.submit(init_database, db)
for db
in self.
_databases
69 for future
in init_db_futures:
73 init_database(database)
79def pytest_addoption(parser):
81 :param parser: pytest's argument parser
83 group = parser.getgroup(
'postgresql')
84 group.addoption(
'--postgresql', help=
'PostgreSQL connection string')
87 help=
'Disable use of PostgreSQL',
91 '--postgresql-keep-existing-db',
94 'Keep existing databases with up-to-date schema. By default '
95 'testsuite will drop and create anew any existing database when '
96 'initializing databases.'
101def pytest_report_header(config):
102 conninfo = _get_connection_info(config)
103 return [f
'PostgreSQL: {conninfo.get_uri()}']
106def pytest_configure(config):
107 config.addinivalue_line(
109 'pgsql: per-test PostgreSQL initialization',
113def pytest_service_register(register_service):
114 register_service(
'postgresql', service.create_pgsql_service)
117@pytest.fixture(scope='session')
118def pgsql_cleanup_exclude_tables() -> frozenset[str]:
123def pgsql(_pgsql, pgsql_apply) -> dict[str, control.PgDatabaseWrapper]:
126 @ref testsuite.databases.pgsql.control.PgDatabaseWrapper dictionary
132 cursor = pgsql['example_db'].cursor()
133 cursor.execute('SELECT ... FROM ...WHERE ...')
134 assert list(cusror) == [...]
137 @ingroup userver_testsuite_fixtures
138 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/pgsql/pytest_plugin.py#L123)
142 for dbname, connection
in _pgsql.items()
146@pytest.fixture(scope='session')
149 pgsql_cleanup_exclude_tables,
151 [list[discover.PgShardedDatabase]],
154 """Creates pgsql configuration.
156 @param databases List of databases.
157 @returns @ref ServiceLocalConfig instance.
159 @ingroup userver_testsuite_fixtures
160 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/pgsql/pytest_plugin.py#L144)
163 def _pgsql_local_create(databases):
167 pgsql_cleanup_exclude_tables,
170 return _pgsql_local_create
173@pytest.fixture(scope='session')
174def pgsql_disabled(pytestconfig) -> bool:
175 return pytestconfig.option.no_postgresql
179def pgsql_local(pgsql_local_create) -> ServiceLocalConfig:
180 """Configures local pgsql instance.
182 @returns @ref ServiceLocalConfig instance.
184 In order to use pgsql fixture you have to override pgsql_local()
185 in your local conftest.py file, example:
188 @pytest.fixture(scope='session')
189 def pgsql_local(pgsql_local_create):
190 databases = discover.find_schemas(
191 'service_name', [PG_SCHEMAS_PATH])
192 return pgsql_local_create(list(databases.values()))
195 Sometimes it is desirable to have tests-only database, maybe used in one
196 particular test or tests group. This can be achieved by by overriding
197 @c pgsql_local fixture in your test file:
201 def pgsql_local(pgsql_local_create):
202 databases = discover.find_schemas(
203 'testsuite', [pathlib.Path('custom/pgsql/schema/path')])
204 return pgsql_local_create(list(databases.values()))
207 @c pgsql_local provides access to PostgreSQL connection parameters:
210 def get_custom_connection_string(pgsql_local):
211 conninfo = pgsql_local['database_name']
212 custom_dsn: str = conninfo.replace(options='-c opt=val').get_dsn()
216 @ingroup userver_testsuite_fixtures
217 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/pgsql/pytest_plugin.py#L173)
222@pytest.fixture(scope='session')
223def pgsql_parallelization_enabled():
232 pgsql_cleanup_exclude_tables,
233 pgsql_disabled: bool,
234 pgsql_parallelization_enabled: bool,
240 pgsql_cleanup_exclude_tables,
242 return pgsql_local.initialize(parallel_init=pgsql_parallelization_enabled)
245@pytest.fixture(scope='session')
246def pgsql_background_truncate_enabled():
251def _pgsql_apply_queries(
252 request, _pgsql: ServiceLocalConfig, _pgsql_query_loader
254 def pgsql_default_queries(dbname):
256 *_pgsql_query_loader.load(
258 'pgsql.default_queries',
261 *_pgsql_query_loader.loaddir(
263 'pgsql.default_queries',
268 def pgsql_mark(dbname, files=(), directories=(), queries=()):
272 result_queries += _pgsql_query_loader.load(path,
'mark.pgsql.files')
273 for path
in directories:
274 result_queries += _pgsql_query_loader.loaddir(
276 'mark.pgsql.directories',
278 for query
in queries:
279 queries_str: typing.Iterable = []
280 if isinstance(query, str):
281 queries_str = [query]
282 elif isinstance(query, (list, tuple)):
286 f
'sql queries of type {type(query)} are not supported',
288 for query_str
in queries_str:
289 result_queries.append(
292 source=
'mark.pgsql.queries',
296 return dbname, result_queries
298 overrides: typing.DefaultDict[
301 ] = collections.defaultdict(list)
302 for mark
in request.node.iter_markers(
'pgsql'):
303 dbname, queries = pgsql_mark(*mark.args, **mark.kwargs)
304 if dbname
not in _pgsql:
306 'Unknown database {}'.format(dbname)
308 overrides[dbname].extend(queries)
312 for dbname
in _pgsql.keys():
313 queries[dbname] = overrides.get(dbname, pgsql_default_queries(dbname))
320 _pgsql: ServiceLocalConfig,
322 pgsql_background_truncate_enabled: bool,
323 pgsql_parallelization_enabled: bool,
324 _pgsql_apply_queries,
326 """Initialize PostgreSQL database with data.
328 By default pg_${DBNAME}.sql and pg_${DBNAME}/*.sql files are used
329 to fill PostgreSQL databases.
331 Use pytest.mark.pgsql to change this behaviour:
337 'pg_foo@0_alternative.sql'
340 'pg_foo@0_alternative_dir'
343 'INSERT INTO foo VALUES (1, 2, 3, 4)',
348 @ingroup userver_testsuite_fixtures
349 Part of the [yandex-taxi-testsuite](https://github.com/yandex/yandex-taxi-testsuite/blob/develop/testsuite/databases/pgsql/pytest_plugin.py#L310)
352 if pgsql_parallelization_enabled:
353 with concurrent.futures.ThreadPoolExecutor()
as executor:
354 db_apply_queries_future = []
355 for dbname, pg_db
in _pgsql.items():
356 db_apply_queries_future.append(
358 pg_db.apply_queries, _pgsql_apply_queries[dbname]
362 for future
in db_apply_queries_future:
366 for dbname, pg_db
in _pgsql.items():
367 pg_db.apply_queries(_pgsql_apply_queries[dbname])
371 if pgsql_background_truncate_enabled:
372 for pg_db
in _pgsql.values():
373 pg_db.schedule_truncation()
377def _pgsql_query_loader(get_file_path, get_directory_path, mockserver_info):
378 def substitute_mockserver(str_val: str):
379 return str_val.replace(
381 f
'http://{mockserver_info.host}:{mockserver_info.port}',
384 def load_pg_file(path, source):
385 query = substitute_mockserver(path.read_text())
390 def load(path, source, missing_ok=False):
391 path = get_file_path(path, missing_ok=missing_ok)
394 return [load_pg_file(path, source)]
397 def loaddir(directory, source, missing_ok=False):
399 directory = get_directory_path(directory, missing_ok=missing_ok)
402 for path
in utils.scan_sql_directory(directory):
403 result.append(load_pg_file(path, source))
412 pgsql_disabled: bool,
413 ensure_service_started,
414 pgsql_local: ServiceLocalConfig,
415 _pgsql_service_settings,
420 and not pytestconfig.option.postgresql
422 ensure_service_started(
'postgresql', settings=_pgsql_service_settings)
425@pytest.fixture(scope='session')
426def _pgsql_control(pytestconfig, pgsql_disabled: bool):
430 _get_connection_info(pytestconfig),
431 verbose=pytestconfig.option.verbose,
432 skip_applied_schemas=(
433 pytestconfig.option.postgresql_keep_existing_db
434 or pytestconfig.option.service_wait
437 with contextlib.closing(instance):
441@pytest.fixture(scope='session')
442def _pgsql_service_settings() -> service.ServiceSettings:
443 return service.get_service_settings()
446@pytest.fixture(scope='session')
449 _pgsql_service_settings,
450) -> connection.PgConnectionInfo:
451 return _get_connection_info(request.config)
454def _get_connection_info(config):
455 connstr = config.option.postgresql
457 return connection.parse_connection_string(connstr)
458 settings = service.get_service_settings()
459 return settings.get_conninfo()