From 17e1a1507b2c85a12b0453c2a6bd936b563a36b7 Mon Sep 17 00:00:00 2001 From: Kristoffer Andersson Date: Fri, 29 Apr 2022 10:01:53 +0200 Subject: [PATCH 1/5] build: add data folder --- data/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/.gitkeep diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 00000000..e69de29b From 0e3a652f43c729b8b8a5ae49c4ec25802060b700 Mon Sep 17 00:00:00 2001 From: Kristoffer Andersson Date: Fri, 29 Apr 2022 10:02:48 +0200 Subject: [PATCH 2/5] build: mark as beta --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01686d87..74abc8a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ homepage = "https://spraakbanken.gu.se" documentation = "https://github.com/spraakbanken/karp-backend" repository = "https://github.com/spraakbanken/karp-backend" classifiers = [ - "Development Status :: 2 - Pre-Alpha", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: Unix", From e18e3335e7d00c07c02193cce9277108ce9fba19 Mon Sep 17 00:00:00 2001 From: Kristoffer Andersson Date: Fri, 29 Apr 2022 15:42:09 +0200 Subject: [PATCH 3/5] feat: add cli entries validate --- karp/cliapp/subapps/entries_subapp.py | 104 +++++- karp/lex/domain/value_objects/entry_schema.py | 3 +- karp/tests/unit/test_json_schema.py | 348 +++++++++++++++++- 3 files changed, 447 insertions(+), 8 deletions(-) diff --git a/karp/cliapp/subapps/entries_subapp.py b/karp/cliapp/subapps/entries_subapp.py index 8dcd375d..95b6b458 100644 --- a/karp/cliapp/subapps/entries_subapp.py +++ b/karp/cliapp/subapps/entries_subapp.py @@ -1,14 +1,21 @@ +import collections.abc import logging from pathlib import Path +import sys from typing import Optional + import json_streams +import json_streams.jsonlib +from sb_json_tools import jt_val import typer -from tabulate import tabulate + +# from tabulate import tabulate from tqdm import tqdm from karp.foundation.commands import CommandBus from karp import lex +from karp.utility import json_schema # from karp.lex.domain.errors import ResourceAlreadyPublished @@ -82,9 +89,102 @@ def export_entries( unit=" entries", ), output, - use_stdout_as_default=None, + use_stdout_as_default=True, ) +class Counter(collections.abc.Generator): + def __init__(self, sink) -> None: + self._counter: int = 0 + self._sink = sink + + @property + def counter(self) -> int: + return self._counter + + def send(self, value): + self._counter += 1 + self._sink.send(value) + + def throw(self, typ=None, val=None, tb=None): + raise StopIteration + + +@subapp.command("validate") +@cli_error_handler +@cli_timer +def validate_entries( + ctx: typer.Context, + path: Optional[Path] = typer.Argument(None), + config_path: Optional[Path] = typer.Option( + None, + "--config", + "-c", + help="resource config", + ), + resource_id_raw: Optional[str] = typer.Option(None, "--resource_id"), + output: Optional[Path] = typer.Option( + None, "--output", "-o", help="file to write to" + ), +): + typer.echo(f"reading from {path if path else 'stdin'} ...", err=True) + err_output = None + + if not output and path: + output = Path(f"{path}.v6.jsonl") + + if not err_output and output: + err_output = Path(f"{output}.errors.jsonl") + + if config_path and resource_id_raw: + typer.echo("You can't provide both '--resource_id' and '--config/-c'", err=True) + raise typer.Exit(301) + + if config_path: + config = json_streams.jsonlib.load_from_file(config_path) + elif resource_id_raw: + repo = inject_from_ctx(lex.ReadOnlyResourceRepository, ctx=ctx) + resource = repo.get_by_resource_id(resource_id_raw) + if resource: + config = resource.config + else: + typer.echo(f"Can't find resource '{resource_id_raw}'", err=True) + raise typer.Exit(302) + else: + typer.echo("You must provide either '--resource_id' or '--config/-c'", err=True) + raise typer.Exit(code=300) + + schema = json_schema.create_entry_json_schema(config["fields"]) + + error_code = 0 + + with json_streams.sink_from_file( + err_output, use_stderr_as_default=True + ) as error_sink, json_streams.sink_from_file( + output, use_stdout_as_default=True + ) as correct_sink: + error_counter = Counter(error_sink) + # error_counter.send(None) + jt_val.processing_validate( + schema, + tqdm( + json_streams.load_from_file(path, use_stdin_as_default=True), + desc="Validating", + unit=" entries", + ), + on_ok=correct_sink, + on_error=error_counter, + ) + if error_counter.counter > 0: + error_code = 130 + print( + f'{error_counter.counter} entries failed validation, see "{err_output}"', + file=sys.stderr, + ) + + if error_code: + raise typer.Exit(error_code) + + def init_app(app): app.add_typer(subapp, name="entries") diff --git a/karp/lex/domain/value_objects/entry_schema.py b/karp/lex/domain/value_objects/entry_schema.py index a8cce860..37695747 100644 --- a/karp/lex/domain/value_objects/entry_schema.py +++ b/karp/lex/domain/value_objects/entry_schema.py @@ -20,7 +20,6 @@ def validate_entry(self, json_obj: dict): self._compiled_schema(json_obj) except fastjsonschema.JsonSchemaException as e: logger.warning( - "Entry not valid", - extra={'entry': json_obj, 'error_message': str(e)} + "Entry not valid", extra={"entry": json_obj, "error_message": str(e)} ) raise errors.InvalidEntry() from e diff --git a/karp/tests/unit/test_json_schema.py b/karp/tests/unit/test_json_schema.py index c86db027..03a64c6b 100644 --- a/karp/tests/unit/test_json_schema.py +++ b/karp/tests/unit/test_json_schema.py @@ -9,12 +9,12 @@ "fields": { "name": { "type": "string", - "required": true + "required": True }, "municipality": { - "collection": true, + "collection": True, "type": "number", - "required": true + "required": True }, "population": { "type": "number" @@ -27,7 +27,7 @@ }, "code": { "type": "number", - "required": true + "required": True } }, "sort": "name", @@ -101,6 +101,346 @@ def test_create_json_schema(json_schema_config): assert json_schema["type"] == "object" +def test_create_complex_json_schema(): + config = { + "fields": { + "id": {"type": "string", "required": True}, + "s_nr": {"type": "integer"}, + "ortografi": {"type": "string", "required": True}, + "ordklass": {"type": "string", "required": True}, + "böjningsklass": {"type": "string", "required": True}, + "artikelkommentar": {"type": "string"}, + "ursprung": {"type": "string", "collection": True}, + "SOLemman": { + "type": "object", + "collection": True, + "fields": { + "l_nr": {"type": "integer", "required": True}, + "s_nr": {"type": "integer", "required": True}, + "lm_sabob": {"type": "integer", "required": True}, + "ortografi": {"type": "string", "required": True}, + "lemmatyp": {"type": "string"}, + "lemmaundertyp": {"type": "string"}, + "stam": {"type": "string"}, + "böjning": {"type": "string"}, + "ordbildning": {"type": "string"}, + "analys": {"type": "string"}, + "sorteringsform": {"type": "string"}, + "kommentar": {"type": "string"}, + "tagg": {"type": "string"}, + "ursprung": {"type": "string"}, + "betydelser": { + "type": "object", + "collection": True, + "required": True, + "fields": { + "x_nr": {"type": "integer", "required": True}, + "status": {"type": "string"}, + "etymologier": { + "type": "object", + "collection": True, + "fields": { + "förstaBelägg": {"type": "string"}, + "källa": {"type": "string"}, + "beskrivning": {"type": "string"}, + "status": {"type": "integer"}, + "kommentar": {"type": "string"}, + }, + }, + "kc_nr": {"type": "integer", "required": True}, + "huvudkommentar": {"type": "string"}, + "formellKommmentar": {"type": "string"}, + "formellKommmentarExempel": {"type": "string"}, + "formellKommmentarTillägg": {"type": "string"}, + "definition": {"type": "string"}, + "definitionstillägg": {"type": "string"}, + "slutkommentar": {"type": "string"}, + "ämnesområden": { + "type": "object", + "collection": True, + "fields": { + "ämne": {"type": "string"}, + "specifikt": {"type": "string"}, + }, + }, + "hänvisningar": { + "type": "object", + "collection": True, + "fields": { + "kc_nr": {"type": "integer"}, + "l_nr": {"type": "integer"}, + "typ": {"type": "string"}, + "hänvisning": {"type": "string"}, + "kommentar": {"type": "string"}, + "status": {"type": "integer"}, + "visas": {"type": "boolean"}, + }, + }, + "morfex": { + "type": "object", + "collection": True, + "fields": { + "ortografi": {"type": "string"}, + "hänvisning": {"type": "string"}, + "kommentar": {"type": "string"}, + "visas": {"type": "boolean"}, + }, + }, + "syntex": { + "type": "object", + "collection": True, + "fields": { + "typ": {"type": "string"}, + "text": {"type": "string"}, + "kommentar": {"type": "string"}, + "visas": {"type": "boolean"}, + }, + }, + "valenser": { + "type": "object", + "collection": True, + "fields": { + "vl_nr": {"type": "integer"}, + "typ": {"type": "string"}, + "prevalens": {"type": "string"}, + "beskrivning": {"type": "string"}, + "kommentar": {"type": "string"}, + "status": {"type": "integer"}, + "visas": {"type": "boolean"}, + }, + }, + "underbetydelser": { + "type": "object", + "collection": True, + "fields": { + "kc_nr": {"type": "integer", "required": True}, + "huvudkommentar": {"type": "string"}, + "typ": {"type": "string"}, + "formellKommmentar": {"type": "string"}, + "formellKommmentarvar": {"type": "string"}, + "formellKommmentarandra": {"type": "string"}, + "defintion": {"type": "string"}, + "definitionstillägg": {"type": "string"}, + "slutkommentar": {"type": "string"}, + "ämnesområden": { + "type": "object", + "collection": True, + "fields": { + "ämne": {"type": "string"}, + "specifikt": {"type": "string"}, + }, + }, + "hänvisningar": { + "type": "object", + "collection": True, + "fields": { + "kc_nr": {"type": "integer"}, + "l_nr": {"type": "integer"}, + "typ": {"type": "string"}, + "hänvisning": {"type": "string"}, + "kommentar": {"type": "string"}, + "status": {"type": "integer"}, + "visas": {"type": "boolean"}, + }, + }, + "morfex": { + "type": "object", + "collection": True, + "fields": { + "ortografi": {"type": "string"}, + "hänvisning": {"type": "string"}, + "kommentar": {"type": "string"}, + "visas": {"type": "boolean"}, + }, + }, + "syntex": { + "type": "object", + "collection": True, + "fields": { + "typ": {"type": "string"}, + "text": {"type": "string"}, + "kommentar": {"type": "string"}, + "visas": {"type": "boolean"}, + }, + }, + "valenser": { + "type": "object", + "collection": True, + "fields": { + "vl_nr": {"type": "integer"}, + "typ": {"type": "string"}, + "prevalens": {"type": "string"}, + "beskrivning": {"type": "string"}, + "kommentar": {"type": "string"}, + "status": {"type": "integer"}, + "visas": {"type": "boolean"}, + }, + }, + }, + }, + "idiom": { + "type": "object", + "collection": True, + "fields": { + "i_nr": {"type": "integer"}, + "hänvisning": {"type": "integer"}, + "idiom": {"type": "string"}, + "formellKommmentar": {"type": "string"}, + "alternativinledare": {"type": "string"}, + "alternativform": {"type": "string"}, + "status": {"type": "integer"}, + "idiombetydelser": { + "type": "object", + "collection": True, + "fields": { + "ix_nr": {"type": "integer"}, + "definitionsinledare": {"type": "string"}, + "huvudkommentar": {"type": "string"}, + "definition": {"type": "string"}, + "definitionstillägg": {"type": "string"}, + "exempel": {"type": "string"}, + "status": {"type": "integer"}, + "visas": {"type": "boolean"}, + }, + }, + "visas": {"type": "boolean"}, + }, + }, + }, + }, + "sentenserOchStilrutor": { + "type": "object", + "collection": True, + "fields": { + "typ": {"type": "string"}, + "text": {"type": "string"}, + "origid": {"type": "integer"}, + "origOrd": {"type": "string"}, + "visas": {"type": "boolean"}, + }, + }, + "uttal": { + "type": "object", + "collection": True, + "fields": { + "visas": {"type": "boolean"}, + "typ": {"type": "string"}, + "lemmaMedTryckangivelse": {"type": "string"}, + "fonetikparentes": {"type": "string"}, + "fonetikkommentar": {"type": "string"}, + "filenamInlästUttal": {"type": "string"}, + }, + }, + "lemma_referenser": { + "type": "object", + "collection": True, + "fields": { + "l_nr": {"type": "integer", "required": True}, + "lm_sabob": {"type": "integer", "required": True}, + "ortografi": {"type": "string", "required": True}, + "lemmatyp": {"type": "string"}, + "lemmaundertyp": {"type": "string"}, + "stam": {"type": "string"}, + "böjning": {"type": "string"}, + "ordbildning": {"type": "string"}, + "analys": {"type": "string"}, + "sorteringsform": {"type": "string"}, + "kommentar": {"type": "string"}, + "tagg": {"type": "string"}, + "ursprung": {"type": "string"}, + }, + }, + }, + }, + "SAOLLemman": { + "type": "object", + "collection": True, + "fields": { + "lemmaId": {"type": "string"}, + "ortografi": {"type": "string", "required": True}, + "homografNr": { + "type": "object", + "fields": { + "nummer": {"type": "string"}, + "version": {"type": "string"}, + }, + }, + "fonetik": { + "type": "object", + "collection": True, + "fields": { + "form": {"type": "string"}, + "kommentar": {"type": "string"}, + }, + }, + "saolKlass": {"type": "string"}, + "analys": {"type": "string"}, + "böjning": {"type": "string"}, + "alt": { + "type": "object", + "collection": True, + "fields": { + "grundform": {"type": "string"}, + "homografNr": { + "type": "object", + "fields": { + "nummer": {"type": "string"}, + "version": {"type": "string"}, + }, + }, + "analys": {"type": "string"}, + "typ": {"type": "string"}, + }, + }, + "fack": {"type": "string", "collection": True}, + "kommentarTillOrdklassSAOL11": {"type": "string"}, + "saol_status": {"type": "integer"}, + "ursprung": {"type": "string"}, + "betydelser": { + "type": "object", + "collection": True, + "fields": { + "id": {"type": "string"}, + "ordningSAOL14": {"type": "string"}, + "definition": {"type": "string"}, + "exempel": { + "type": "object", + "collection": True, + "fields": { + "text": {"type": "string"}, + "parafras": {"type": "string"}, + }, + }, + "huvudkommentar": {"type": "string"}, + "formellKommentar": {"type": "string", "collection": True}, + }, + }, + "relation": { + "type": "object", + "collection": True, + "fields": { + "typ": {"type": "string"}, + "till_id": {"type": "string"}, + }, + }, + "kommentar": {"type": "string"}, + "ptv": {"type": "string"}, + "smdb_n": {"type": "string"}, + "hänvisningar": { + "type": "object", + "collection": True, + "fields": { + "typ": {"type": "string"}, + "till_id": {"type": "string"}, + }, + }, + }, + }, + } + } + _json_schema = create_entry_json_schema(config["fields"]) + + class TestCreateJsonSchema: @pytest.mark.parametrize("field_type", ["long_string"]) def test_create_with_type(self, field_type: str): From 4dce02fd3acb3090cf440b9a112e6e3132d7a2e2 Mon Sep 17 00:00:00 2001 From: Kristoffer Andersson Date: Mon, 2 May 2022 15:10:17 +0200 Subject: [PATCH 4/5] fix: fetch correct --- karp/lex/application/queries/resources.py | 19 ++- karp/lex_infrastructure/queries/resources.py | 144 ++++++++++-------- .../repositories/sql_resources.py | 44 +++--- 3 files changed, 126 insertions(+), 81 deletions(-) diff --git a/karp/lex/application/queries/resources.py b/karp/lex/application/queries/resources.py index 6c2f378d..3d18bc24 100644 --- a/karp/lex/application/queries/resources.py +++ b/karp/lex/application/queries/resources.py @@ -39,8 +39,25 @@ def query(self, resource_id: str) -> UniqueId: class ReadOnlyResourceRepository(abc.ABC): + def get_by_resource_id( + self, resource_id: str, version: Optional[int] = None + ) -> Optional[ResourceDto]: + resource = self._get_by_resource_id(resource_id) + if not resource: + return None + + if version is not None: + resource = self.get_by_id(resource.entity_id, version=version) + return resource + + @abc.abstractmethod + def get_by_id( + self, entity_id: UniqueId, version: Optional[int] = None + ) -> Optional[ResourceDto]: + pass + @abc.abstractmethod - def get_by_resource_id(self, resource_id: str, version: Optional[int] = None) -> Optional[ResourceDto]: + def _get_by_resource_id(self, resource_id: str) -> Optional[ResourceDto]: pass @abc.abstractmethod diff --git a/karp/lex_infrastructure/queries/resources.py b/karp/lex_infrastructure/queries/resources.py index fca2f183..4c44e79e 100644 --- a/karp/lex_infrastructure/queries/resources.py +++ b/karp/lex_infrastructure/queries/resources.py @@ -2,120 +2,140 @@ import sqlalchemy as sa from sqlalchemy import sql +from karp.foundation.value_objects.unique_id import UniqueId -from karp.lex.application.queries import GetPublishedResources, ResourceDto, GetResources +from karp.lex.application.queries import ( + GetPublishedResources, + ResourceDto, + GetResources, +) from karp.lex.application.queries.resources import ReadOnlyResourceRepository from karp.lex.domain.entities import resource from karp.lex_infrastructure.sql.sql_models import ResourceModel from karp.lex_infrastructure.queries.base import SqlQuery -class SqlGetPublishedResources( - GetPublishedResources, - SqlQuery -): +class SqlGetPublishedResources(GetPublishedResources, SqlQuery): def query(self) -> Iterable[ResourceDto]: - subq = sql.select( - ResourceModel.entity_id, - sa.func.max(ResourceModel.last_modified).label('maxdate') - ).group_by(ResourceModel.entity_id).subquery('t2') + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), + ) + .group_by(ResourceModel.entity_id) + .subquery("t2") + ) stmt = sql.select(ResourceModel).join( subq, sa.and_( ResourceModel.entity_id == subq.c.entity_id, ResourceModel.last_modified == subq.c.maxdate, - ResourceModel.is_published == True - ) - ) - return ( - _row_to_dto(row) - for row in self._conn.execute(stmt) + ResourceModel.is_published == True, + ), ) + return (_row_to_dto(row) for row in self._conn.execute(stmt)) -class SqlGetResources( - GetResources, - SqlQuery -): +class SqlGetResources(GetResources, SqlQuery): def query(self) -> Iterable[ResourceDto]: - subq = sql.select( - ResourceModel.entity_id, - sa.func.max(ResourceModel.last_modified).label('maxdate') - ).group_by(ResourceModel.entity_id).subquery('t2') + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), + ) + .group_by(ResourceModel.entity_id) + .subquery("t2") + ) stmt = sql.select(ResourceModel).join( subq, sa.and_( ResourceModel.entity_id == subq.c.entity_id, ResourceModel.last_modified == subq.c.maxdate, - ) - ) - return ( - _row_to_dto(row) - for row in self._conn.execute(stmt) + ), ) + return (_row_to_dto(row) for row in self._conn.execute(stmt)) -class SqlReadOnlyResourceRepository( - ReadOnlyResourceRepository, - SqlQuery -): - def get_by_resource_id( - self, - resource_id: str, - version: Optional[int] = None +class SqlReadOnlyResourceRepository(ReadOnlyResourceRepository, SqlQuery): + def get_by_id( + self, entity_id: UniqueId, version: Optional[int] = None ) -> Optional[ResourceDto]: - filters: dict[str, str | int] = { - 'resource_id': resource_id - } + filters: dict[str, UniqueId | str | int] = {"entity_id": entity_id} if version: - filters['version'] = version - stmt = sql.select( - ResourceModel - ).filter_by(**filters).order_by( - ResourceModel.last_modified.desc() + filters["version"] = version + stmt = ( + sql.select(ResourceModel) + .filter_by(**filters) + .order_by(ResourceModel.last_modified.desc()) ) + print(f"stmt={str(stmt)}") row = self._conn.execute(stmt).first() return _row_to_dto(row) if row else None - def get_published_resources(self) -> Iterable[ResourceDto]: - subq = sql.select( - ResourceModel.entity_id, - sa.func.max(ResourceModel.last_modified).label('maxdate') - ).group_by(ResourceModel.entity_id).subquery('t2') + def _get_by_resource_id(self, resource_id: str) -> Optional[ResourceDto]: + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), + ) + .group_by(ResourceModel.entity_id) + .subquery("t2") + ) stmt = sql.select(ResourceModel).join( subq, sa.and_( ResourceModel.entity_id == subq.c.entity_id, ResourceModel.last_modified == subq.c.maxdate, - ResourceModel.is_published == True + ResourceModel.resource_id == resource_id, + ), + ) + stmt = stmt.order_by(ResourceModel.last_modified.desc()) + row = self._conn.execute(stmt).first() + + return _row_to_dto(row) if row else None + + def get_published_resources(self) -> Iterable[ResourceDto]: + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), ) + .group_by(ResourceModel.entity_id) + .subquery("t2") ) - return ( - _row_to_dto(row) - for row in self._conn.execute(stmt) + + stmt = sql.select(ResourceModel).join( + subq, + sa.and_( + ResourceModel.entity_id == subq.c.entity_id, + ResourceModel.last_modified == subq.c.maxdate, + ResourceModel.is_published == True, + ), ) + return (_row_to_dto(row) for row in self._conn.execute(stmt)) def get_all_resources(self) -> Iterable[ResourceDto]: - subq = sql.select( - ResourceModel.entity_id, - sa.func.max(ResourceModel.last_modified).label('maxdate') - ).group_by(ResourceModel.entity_id).subquery('t2') + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), + ) + .group_by(ResourceModel.entity_id) + .subquery("t2") + ) stmt = sql.select(ResourceModel).join( subq, sa.and_( ResourceModel.entity_id == subq.c.entity_id, ResourceModel.last_modified == subq.c.maxdate, - ) - ) - return ( - _row_to_dto(row) - for row in self._conn.execute(stmt) + ), ) + return (_row_to_dto(row) for row in self._conn.execute(stmt)) def _row_to_dto(row_proxy) -> ResourceDto: diff --git a/karp/lex_infrastructure/repositories/sql_resources.py b/karp/lex_infrastructure/repositories/sql_resources.py index e651a60b..d688f1a9 100644 --- a/karp/lex_infrastructure/repositories/sql_resources.py +++ b/karp/lex_infrastructure/repositories/sql_resources.py @@ -46,10 +46,14 @@ def _save(self, resource: Resource): def resource_ids(self) -> List[str]: self._check_has_session() - subq = sql.select( - ResourceModel.entity_id, - sa.func.max(ResourceModel.last_modified).label('maxdate') - ).group_by(ResourceModel.entity_id).subquery('t2') + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), + ) + .group_by(ResourceModel.entity_id) + .subquery("t2") + ) stmt = sql.select(ResourceModel.resource_id).join( subq, @@ -57,7 +61,7 @@ def resource_ids(self) -> List[str]: ResourceModel.entity_id == subq.c.entity_id, ResourceModel.last_modified == subq.c.maxdate, ResourceModel.discarded == False, - ) + ), ) return self._session.execute(stmt).scalars().all() # query = self._session.query(ResourceModel) @@ -80,10 +84,14 @@ def _by_resource_id( resource_id: str, ) -> Optional[Resource]: self._check_has_session() - subq = sql.select( - ResourceModel.entity_id, - sa.func.max(ResourceModel.last_modified).label('maxdate') - ).group_by(ResourceModel.entity_id).subquery('t2') + subq = ( + sql.select( + ResourceModel.entity_id, + sa.func.max(ResourceModel.last_modified).label("maxdate"), + ) + .group_by(ResourceModel.entity_id) + .subquery("t2") + ) stmt = sql.select(ResourceModel).join( subq, @@ -91,10 +99,10 @@ def _by_resource_id( ResourceModel.entity_id == subq.c.entity_id, ResourceModel.last_modified == subq.c.maxdate, ResourceModel.resource_id == resource_id, - ) + ), ) - stmt = stmt.order_by(ResourceModel.last_modified.desc()) + query = self._session.execute(stmt).scalars() resource_dto = query.first() return resource_dto.to_entity() if resource_dto else None @@ -119,7 +127,9 @@ def get_latest_version(self, resource_id: str) -> int: return 0 return row.version - def history_by_resource_id(self, resource_id: str) -> typing.List[entities.Resource]: + def history_by_resource_id( + self, resource_id: str + ) -> typing.List[entities.Resource]: self._check_has_session() query = self._session.query(ResourceModel) return [ @@ -216,10 +226,7 @@ def _resource_to_dict(self, resource: Resource) -> typing.Dict: } -class SqlResourceUnitOfWork( - SqlUnitOfWork, - repositories.ResourceUnitOfWork -): +class SqlResourceUnitOfWork(SqlUnitOfWork, repositories.ResourceUnitOfWork): def __init__( self, event_bus: EventBus, @@ -228,7 +235,7 @@ def __init__( session: Optional[Session] = None, ): if not session and not session_factory: - raise ValueError('Both session and session_factory cannot be None') + raise ValueError("Both session and session_factory cannot be None") SqlUnitOfWork.__init__(self, session=session) repositories.ResourceUnitOfWork.__init__(self, event_bus) self.session_factory = session_factory @@ -237,7 +244,7 @@ def __init__( def _begin(self): if self._session_is_created_here: self._session = self.session_factory() # type: ignore - logger.info('using session', extra={'session': self._session}) + logger.info("using session", extra={"session": self._session}) self._resources = SqlResourceRepository(self._session) return self @@ -246,6 +253,7 @@ def repo(self) -> SqlResourceRepository: if self._resources is None: raise RuntimeError("No resources") return self._resources + # def _resource_to_row( # self, resource: Resource # ) -> Tuple[ From 996b88c5614819f448e1f8d0163d0c1a6f6425f3 Mon Sep 17 00:00:00 2001 From: Kristoffer Andersson Date: Tue, 3 May 2022 08:03:13 +0200 Subject: [PATCH 5/5] test: impl adapter --- karp/tests/unit/lex/adapters.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/karp/tests/unit/lex/adapters.py b/karp/tests/unit/lex/adapters.py index 1b9aed6b..902fff26 100644 --- a/karp/tests/unit/lex/adapters.py +++ b/karp/tests/unit/lex/adapters.py @@ -68,8 +68,14 @@ class InMemoryReadResourceRepository(ReadOnlyResourceRepository): def __init__(self, resources: Dict): self.resources = resources - def get_by_resource_id( - self, resource_id: str, version=None + def get_by_id(self, entity_id: UniqueId, version: Optional[int] = None) -> Optional[ResourceDto]: + resource = self.resources.get(entity_id) + if resource: + return self._row_to_dto(resource) + return None + + def _get_by_resource_id( + self, resource_id: str ) -> Optional[ResourceDto]: return next( (