Skip to content

Commit d359571

Browse files
Remove schema prefix from Hasura root fields, add integration test (#123)
1 parent 11040a4 commit d359571

File tree

14 files changed

+115
-1164
lines changed

14 files changed

+115
-1164
lines changed

poetry.lock

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ pytest = "^3.0"
5252
pytest-cov = "^2.4"
5353
flake8 = "3.9.0"
5454
flakehell = "^0.9.0"
55+
testcontainers = "^3.4.1"
5556

5657
[tool.poetry.extras]
5758
pytezos = ["pytezos"]

src/dipdup/config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,9 @@ class PostgresDatabaseConfig:
7171

7272
kind: Literal['postgres']
7373
host: str
74-
port: int
75-
user: str
76-
database: str
74+
user: str = 'postgres'
75+
database: str = 'postgres'
76+
port: int = 5432
7777
schema_name: str = 'public'
7878
password: str = ''
7979
immune_tables: Optional[List[str]] = None

src/dipdup/hasura.py

Lines changed: 53 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from dipdup.config import HasuraConfig, HTTPConfig, PostgresDatabaseConfig
1717
from dipdup.exceptions import ConfigurationError
1818
from dipdup.http import HTTPGateway
19-
from dipdup.utils import iter_files, iter_models, pascal_to_snake
19+
from dipdup.utils import iter_files, iter_models, pascal_to_snake, remove_prefix
2020

2121
_get_fields_query = '''
2222
query introspectionQuery($name: String!) {
@@ -47,7 +47,7 @@
4747
@dataclass
4848
class Field:
4949
name: str
50-
type: Optional[str]
50+
type: Optional[str] = None
5151

5252
def camelize(self) -> 'Field':
5353
return Field(
@@ -57,8 +57,8 @@ def camelize(self) -> 'Field':
5757

5858
@property
5959
def root(self) -> str:
60-
# NOTE: Hasura omits default schema name in root field name
61-
return humps.decamelize(self.name).lstrip('public_')
60+
# NOTE: Hasura omits schema name prefix in root fields
61+
return remove_prefix(humps.decamelize(self.name), 'public')
6262

6363

6464
class HasuraError(RuntimeError):
@@ -94,16 +94,15 @@ async def configure(self) -> None:
9494
metadata = await self._fetch_metadata()
9595

9696
# NOTE: Hasura metadata updated in three steps.
97-
# NOTE: Order matters because queries must be generated after applying camelcase to model names.
97+
# NOTE: Order matters because queries must be generated after applying table customization to be valid.
9898
# NOTE: 1. Generate and apply tables metadata.
9999
source_tables_metadata = await self._generate_source_tables_metadata()
100100
metadata['sources'][0]['tables'] = source_tables_metadata
101101
await self._replace_metadata(metadata)
102102

103-
# NOTE: 2. Apply camelcase and refresh metadata
104-
if self._hasura_config.camel_case:
105-
await self._apply_camelcase()
106-
metadata = await self._fetch_metadata()
103+
# NOTE: 2. Apply table customization and refresh metadata
104+
await self._apply_table_customization()
105+
metadata = await self._fetch_metadata()
107106

108107
# NOTE: 3. Generate and apply queries and rest endpoints
109108
query_collections_metadata = await self._generate_query_collections_metadata()
@@ -150,6 +149,17 @@ async def _healthcheck(self) -> None:
150149
else:
151150
raise HasuraError(f'Hasura instance not responding for {self._hasura_config.connection_timeout} seconds')
152151

152+
version_json = await (
153+
await self._http._session.get(
154+
f'{self._hasura_config.url}/v1/version',
155+
)
156+
).json()
157+
version = version_json['version']
158+
if version.startswith('v1'):
159+
raise HasuraError('Hasura v1 is not supported.')
160+
161+
self._logger.info('Connected to Hasura %s', version)
162+
153163
async def _reset_metadata(self) -> None:
154164
self._logger.info('Resetting metadata')
155165
await self._hasura_request(
@@ -191,6 +201,13 @@ async def _get_views(self) -> List[str]:
191201
self._logger.info('Found %s regular and materialized views', len(views))
192202
return views
193203

204+
def _iterate_graphql_queries(self) -> Iterator[Tuple[str, str]]:
205+
package = importlib.import_module(self._package)
206+
package_path = dirname(package.__file__)
207+
graphql_path = join(package_path, 'graphql')
208+
for file in iter_files(graphql_path, '.graphql'):
209+
yield file.name.split('/')[-1][:-8], file.read()
210+
194211
async def _generate_source_tables_metadata(self) -> List[Dict[str, Any]]:
195212
"""Generate source tables metadata based on project models and views.
196213
@@ -235,13 +252,6 @@ async def _generate_source_tables_metadata(self) -> List[Dict[str, Any]]:
235252

236253
return list(metadata_tables.values())
237254

238-
def _iterate_graphql_queries(self) -> Iterator[Tuple[str, str]]:
239-
package = importlib.import_module(self._package)
240-
package_path = dirname(package.__file__)
241-
graphql_path = join(package_path, 'graphql')
242-
for file in iter_files(graphql_path, '.graphql'):
243-
yield file.name.split('/')[-1][:-8], file.read()
244-
245255
async def _generate_query_collections_metadata(self) -> List[Dict[str, Any]]:
246256
queries = []
247257
for _, model in iter_models(self._package):
@@ -283,8 +293,7 @@ async def _get_fields_json(self, name: str) -> List[Dict[str, Any]]:
283293
raise HasuraError(f'Unknown table `{name}`') from e
284294

285295
async def _get_fields(self, name: str = 'query_root') -> List[Field]:
286-
# NOTE: Hasura omits default schema name
287-
name = name.lstrip('public_')
296+
name = Field(name).root
288297

289298
try:
290299
fields_json = await self._get_fields_json(name)
@@ -320,17 +329,17 @@ async def _get_fields(self, name: str = 'query_root') -> List[Field]:
320329

321330
return fields
322331

323-
async def _apply_camelcase(self) -> None:
332+
async def _apply_table_customization(self) -> None:
324333
"""Convert table and column names to camelCase.
325334
326335
Based on https://github.com/m-rgba/hasura-snake-to-camel
327336
"""
328-
self._logger.info('Converting field names to camelCase')
329337

330338
tables = await self._get_fields()
331339

340+
# TODO: Bulk request
332341
for table in tables:
333-
custom_root_fields = self._format_custom_root_fields(table.root)
342+
custom_root_fields = self._format_custom_root_fields(table)
334343
columns = await self._get_fields(table.root)
335344
custom_column_names = self._format_custom_column_names(columns)
336345
args: Dict[str, Any] = {
@@ -343,6 +352,7 @@ async def _apply_camelcase(self) -> None:
343352
},
344353
}
345354

355+
self._logger.info('Applying `%s` table customization', table.root)
346356
await self._hasura_request(
347357
endpoint='metadata',
348358
json={
@@ -388,22 +398,32 @@ def _format_rest_endpoint(self, query_name: str) -> Dict[str, Any]:
388398
"comment": None,
389399
}
390400

391-
def _format_custom_root_fields(self, table: str) -> Dict[str, Any]:
401+
def _format_custom_root_fields(self, table: Field) -> Dict[str, Any]:
402+
table_name = remove_prefix(table.root, self._database_config.schema_name)
403+
404+
def _fmt(fmt: str) -> str:
405+
if self._hasura_config.camel_case:
406+
return humps.camelize(fmt.format(table_name))
407+
return humps.decamelize(fmt.format(table_name))
408+
392409
# NOTE: Do not change original Hasura format, REST endpoints generation will be broken otherwise
393410
return {
394-
'select': humps.camelize(table),
395-
'select_by_pk': humps.camelize(f'{table}_by_pk'),
396-
'select_aggregate': humps.camelize(f'{table}_aggregate'),
397-
'insert': humps.camelize(f'insert_{table}'),
398-
'insert_one': humps.camelize(f'insert_{table}_one'),
399-
'update': humps.camelize(f'update_{table}'),
400-
'update_by_pk': humps.camelize(f'update_{table}_by_pk'),
401-
'delete': humps.camelize(f'delete_{table}'),
402-
'delete_by_pk': humps.camelize(f'delete_{table}_by_pk'),
411+
'select': _fmt('{}'),
412+
'select_by_pk': _fmt('{}_by_pk'),
413+
'select_aggregate': _fmt('{}_aggregate'),
414+
'insert': _fmt('insert_{}'),
415+
'insert_one': _fmt('insert_{}_one'),
416+
'update': _fmt('update_{}'),
417+
'update_by_pk': _fmt('update_{}_by_pk'),
418+
'delete': _fmt('delete_{}'),
419+
'delete_by_pk': _fmt('delete_{}_by_pk'),
403420
}
404421

405422
def _format_custom_column_names(self, fields: List[Field]) -> Dict[str, Any]:
406-
return {humps.decamelize(f.name): humps.camelize(f.name) for f in fields}
423+
if self._hasura_config.camel_case:
424+
return {humps.decamelize(f.name): humps.camelize(f.name) for f in fields}
425+
else:
426+
return {humps.decamelize(f.name): humps.decamelize(f.name) for f in fields}
407427

408428
def _format_table(self, name: str) -> Dict[str, Any]:
409429
return {
@@ -418,8 +438,7 @@ def _format_table(self, name: str) -> Dict[str, Any]:
418438
def _format_table_table(self, name: str) -> Dict[str, Any]:
419439
return {
420440
"schema": self._database_config.schema_name,
421-
# NOTE: Remove schema prefix from table name
422-
'name': name.replace(self._database_config.schema_name, '').strip('_'),
441+
'name': remove_prefix(name, self._database_config.schema_name),
423442
}
424443

425444
def _format_array_relationship(

src/dipdup/utils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ async def tortoise_wrapper(url: str, models: Optional[str] = None) -> AsyncItera
7979
db_url=url,
8080
modules=modules, # type: ignore
8181
)
82-
except ConnectionRefusedError:
82+
except (OSError, ConnectionRefusedError):
8383
_logger.warning('Can\'t establish database connection, attempt %s/%s', attempt, attempts)
8484
if attempt == attempts - 1:
8585
raise
@@ -232,3 +232,10 @@ def import_from(module: str, obj: str) -> Any:
232232
return getattr(importlib.import_module(module), obj)
233233
except (ImportError, AttributeError) as e:
234234
raise HandlerImportError(module, obj) from e
235+
236+
237+
def remove_prefix(text: str, prefix: str) -> str:
238+
"""Remove prefix and strip underscores"""
239+
if text.startswith(prefix):
240+
text = text[len(prefix) :]
241+
return text.strip('_')

tests/integration_tests/hasura/empty.json

Lines changed: 0 additions & 25 deletions
This file was deleted.

tests/integration_tests/hasura/query_dipdup_state.json

Lines changed: 0 additions & 58 deletions
This file was deleted.

0 commit comments

Comments
 (0)