diff --git a/src/dipdup/hasura.py b/src/dipdup/hasura.py index 182cac1a7..10de706a4 100644 --- a/src/dipdup/hasura.py +++ b/src/dipdup/hasura.py @@ -17,6 +17,10 @@ _logger = logging.getLogger(__name__) +class HasuraError(RuntimeError): + ... + + def _is_model_class(obj) -> bool: """Is subclass of tortoise.Model, but not the base class""" return isinstance(obj, type) and issubclass(obj, Model) and obj != Model and not getattr(obj.Meta, 'abstract', False) @@ -51,11 +55,11 @@ def _format_object_relationship(name: str, column: str) -> Dict[str, Any]: } -def _format_select_permissions(columns: List[str]) -> Dict[str, Any]: +def _format_select_permissions() -> Dict[str, Any]: return { "role": "user", "permission": { - "columns": sorted(columns), + "columns": "*", "filter": {}, "allow_aggregations": True, }, @@ -90,11 +94,13 @@ def _iter_models(*modules) -> Iterator[Tuple[str, Type[Model]]]: yield app, model -async def generate_hasura_metadata(config: DipDupConfig) -> Dict[str, Any]: +async def generate_hasura_metadata(config: DipDupConfig, views: List[str]) -> Dict[str, Any]: """Generate metadata based on dapp models. Includes tables and their relations (but not entities created during execution of snippets from `sql` package directory) """ + if not isinstance(config.database, PostgresDatabaseConfig): + raise RuntimeError _logger.info('Generating Hasura metadata') metadata_tables = {} model_tables = {} @@ -105,24 +111,31 @@ async def generate_hasura_metadata(config: DipDupConfig) -> Dict[str, Any]: for app, model in _iter_models(models, int_models): table_name = model._meta.db_table or pascal_to_snake(model.__name__) # pylint: disable=protected-access model_tables[f'{app}.{model.__name__}'] = table_name - - table = _format_table( + metadata_tables[table_name] = _format_table( name=table_name, - schema=config.database.schema_name if isinstance(config.database, PostgresDatabaseConfig) else 'public', + schema=config.database.schema_name, + ) + + for view in views: + metadata_tables[view] = _format_table( + name=view, + schema=config.database.schema_name, + ) + metadata_tables[view]['select_permissions'].append( + _format_select_permissions(), ) - metadata_tables[table_name] = table for app, model in _iter_models(models, int_models): table_name = model_tables[f'{app}.{model.__name__}'] metadata_tables[table_name]['select_permissions'].append( - _format_select_permissions(list(model._meta.db_fields)), + _format_select_permissions(), ) for field in model._meta.fields_map.values(): if isinstance(field, fields.relational.ForeignKeyFieldInstance): if not isinstance(field.related_name, str): - raise Exception(f'`related_name` of `{field}` must be set') + raise HasuraError(f'`related_name` of `{field}` must be set') related_table_name = model_tables[field.model_name] metadata_tables[table_name]['object_relationships'].append( _format_object_relationship( @@ -135,7 +148,7 @@ async def generate_hasura_metadata(config: DipDupConfig) -> Dict[str, Any]: related_name=field.related_name, table=table_name, column=field.model_field_name + '_id', - schema=config.database.schema_name if isinstance(config.database, PostgresDatabaseConfig) else 'public', + schema=config.database.schema_name, ) ) @@ -152,7 +165,16 @@ async def configure_hasura(config: DipDupConfig): _logger.info('Configuring Hasura') url = config.hasura.url.rstrip("/") - hasura_metadata = await generate_hasura_metadata(config) + views = [ + row[0] + for row in ( + await get_connection(None).execute_query( + f"SELECT table_name FROM information_schema.views WHERE table_schema = '{config.database.schema_name}'" + ) + )[1] + ] + + hasura_metadata = await generate_hasura_metadata(config, views) async with aiohttp.ClientSession() as session: _logger.info('Waiting for Hasura instance to be healthy') @@ -163,8 +185,7 @@ async def configure_hasura(config: DipDupConfig): break await asyncio.sleep(1) else: - _logger.error('Hasura instance not responding for 60 seconds') - return + raise HasuraError('Hasura instance not responding for 60 seconds') headers = {} if config.hasura.admin_secret: @@ -178,7 +199,7 @@ async def configure_hasura(config: DipDupConfig): data=json.dumps( { "type": "export_metadata", - "args": hasura_metadata, + "args": {}, }, ), headers=headers, @@ -204,29 +225,6 @@ async def configure_hasura(config: DipDupConfig): headers=headers, ) if result.get('message') != 'success': - _logger.error('Can\'t configure Hasura instance: %s', result) - return - - views = await get_connection(None).execute_query( - f"SELECT table_name FROM information_schema.views WHERE table_schema = '{config.database.schema_name}'" - ) - for view in views[1]: - result = await http_request( - session, - 'post', - url=f'{url}/v1/query', - data=json.dumps( - { - "type": "add_existing_table_or_view", - "args": { - "name": view[0], - "schema": config.database.schema_name, - }, - }, - ), - headers=headers, - ) - if result.get('message') != 'success' and result.get('code') != 'already-tracked': - _logger.error('Can\'t configure Hasura instance: %s', result) + raise HasuraError('Can\'t configure Hasura instance', result) _logger.info('Hasura instance has been configured')