Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for discarding entry-repositories #195

Merged
merged 12 commits into from
Mar 29, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## Latest Changes

* Avoid name clashes in SqlEntryRepository. PR [#194](https://github.com/spraakbanken/karp-backend/pull/194) by [@kod-kristoff](https://github.com/kod-kristoff).
* Fix correct fetching from repos. PR [#193](https://github.com/spraakbanken/karp-backend/pull/193) by [@kod-kristoff](https://github.com/kod-kristoff).
## 6.0.17

Expand Down
20 changes: 18 additions & 2 deletions karp/cliapp/subapps/entry_repo_subapp.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
from typing import Optional

from tabulate import tabulate
import typer

from karp import lex
from karp.foundation.value_objects import UniqueId
from karp.foundation.commands import CommandBus
from karp.lex.application.queries import ListEntryRepos
from karp.lex.domain.commands import CreateEntryRepository
Expand All @@ -15,13 +17,11 @@

@subapp.command()
def create(infile: typer.FileBinaryRead, ctx: typer.Context):
typer.echo(infile.name)
try:
data = json.load(infile)
except Exception as err:
typer.echo(f"Error reading file '{infile.name}': {str(err)}")
raise typer.Exit(123)
typer.echo('after json.load')
create_entry_repo = CreateEntryRepository.from_dict(
data,
user='local admin',
Expand All @@ -39,6 +39,22 @@ def create(infile: typer.FileBinaryRead, ctx: typer.Context):
)


@subapp.command()
def delete(
entity_id: UniqueId,
ctx: typer.Context,
user: Optional[str] = typer.Option(None),
):

bus = inject_from_ctx(CommandBus, ctx)

delete_entry_repo = DeleteEntryRepo(
entity_id=entity_id,
user=user or "local admin"
)
typer.echo(f"Entry repository with id '{entity_id}' deleted.")


@subapp.command()
def list(ctx: typer.Context):
query = inject_from_ctx(ListEntryRepos, ctx)
Expand Down
13 changes: 5 additions & 8 deletions karp/cliapp/subapps/resource_subapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,11 @@ def show(
raise typer.Exit(3)

typer.echo(
"""
Resource: {resource.resource_id}
EntityId: {resource.entity_id}
Version: {resource.version}
Discarded: {resource.discarded}
Config: {resource.config}
""".format(
resource=resource
tabulate(
(
(key, value)
for key, value in resource.dict().items()
)
)
)

Expand Down
7 changes: 3 additions & 4 deletions karp/foundation/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,10 @@ def stamp(self, user, *, timestamp: float = None):
self._last_modified_by = user
self._last_modified = monotonic_utc_now() if timestamp is None else timestamp

def discard(self, *, user, last_modified: float, timestamp: float = None):
def discard(self, *, user, timestamp: float = None):
self._check_not_discarded()
self._validate_last_modified(last_modified)
super.discard()
# self._validate_last_modified(last_modified)
super().discard()
self._last_modified_by = user
self._last_modified = self._ensure_timestamp(timestamp)

Expand Down Expand Up @@ -236,4 +236,3 @@ def update(
self._last_modified_by = user
self._last_modified = self._ensure_timestamp(timestamp)
self._increment_version()

4 changes: 4 additions & 0 deletions karp/foundation/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,7 @@ def _by_id(
self, id_: Union[uuid.UUID, str], *, version: Optional[int] = None
) -> Optional[EntityType]:
raise NotImplementedError()

@abc.abstractmethod
def num_entities(self) -> int:
...
11 changes: 11 additions & 0 deletions karp/lex/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from karp.lex.domain.commands import (
CreateEntryRepository,
CreateResource,
DeleteEntryRepository,
)
from karp.lex.domain import commands
from karp.lex.domain.value_objects import EntrySchema
Expand All @@ -19,6 +20,7 @@
CreatingEntryRepo,
CreatingResource,
DeletingEntry,
DeletingEntryRepository,
DeletingResource,
PublishingResource,
UpdatingEntry,
Expand Down Expand Up @@ -55,6 +57,15 @@ def create_entry_repository(
entry_repo_uow=uow,
)

@injector.provider
def deleting_entry_repository(
self,
uow: EntryUowRepositoryUnitOfWork,
) -> CommandHandler[DeleteEntryRepository]:
return DeletingEntryRepository(
entry_repo_uow=uow,
)

@injector.provider
def create_resource(
self,
Expand Down
3 changes: 3 additions & 0 deletions karp/lex/application/repositories/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,6 @@ def config(self) -> Dict:
@property
def message(self) -> str:
return self._message

def discard(self, *, user, timestamp: Optional[float] = None):
return entity.TimestampedEntity.discard(self, user=user, timestamp=timestamp)
2 changes: 1 addition & 1 deletion karp/lex/application/use_cases/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
DeletingEntry,
UpdatingEntry,
)
from .entry_repo_handlers import CreatingEntryRepo
from .entry_repo_handlers import CreatingEntryRepo, DeletingEntryRepository
from .resource_handlers import (
CreatingResource,
DeletingResource,
Expand Down
18 changes: 18 additions & 0 deletions karp/lex/application/use_cases/entry_repo_handlers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
from typing import Any

from karp.foundation.commands import CommandHandler
from karp.lex.domain import commands
Expand Down Expand Up @@ -37,3 +38,20 @@ def execute(
uow.repo.save(entry_repo)
uow.commit()
return entry_repo


class DeletingEntryRepository(CommandHandler[commands.DeleteEntryRepository]):
def __init__(
self,
entry_repo_uow: repositories.EntryUowRepositoryUnitOfWork,
**kwargs,
):
self._entry_repo_uow = entry_repo_uow

def execute(self, command: commands.DeleteEntryRepository) -> None:
with self._entry_repo_uow as uow:
entry_repo = uow.repo.get_by_id(command.entity_id)
entry_repo.discard(
user=command.user, timestamp=command.timestamp)
uow.repo.save(entry_repo)
uow.commit()
2 changes: 1 addition & 1 deletion karp/lex/domain/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
DeleteEntry,
UpdateEntry,
)
from .entry_repo_commands import CreateEntryRepository
from .entry_repo_commands import CreateEntryRepository, DeleteEntryRepository
from .resource_commands import (
CreateResource, DeleteResource, PublishResource, UpdateResource)
6 changes: 6 additions & 0 deletions karp/lex/domain/commands/entry_repo_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,9 @@ def from_dict(
user=user,
message=message or 'Entry repository created'
)


class DeleteEntryRepository(Command):
entity_id: UniqueId
message: str
user: str
8 changes: 5 additions & 3 deletions karp/lex_infrastructure/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
)
from karp.lex_infrastructure.repositories import (
SqlEntryUowRepositoryUnitOfWork,
SqlEntryUowCreator,
SqlEntryUowV1Creator,
SqlEntryUowV2Creator,
SqlResourceUnitOfWork,
)

Expand Down Expand Up @@ -102,8 +103,9 @@ def resources_uow(
@injector.multiprovider
def entry_uow_creator_map(self) -> Dict[str, EntryUnitOfWorkCreator]:
return {
'default': SqlEntryUowCreator,
SqlEntryUowCreator.repository_type: SqlEntryUowCreator,
'default': SqlEntryUowV2Creator,
SqlEntryUowV1Creator.repository_type: SqlEntryUowV1Creator,
SqlEntryUowV2Creator.repository_type: SqlEntryUowV2Creator,
}


Expand Down
2 changes: 1 addition & 1 deletion karp/lex_infrastructure/repositories/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .sql_entry_uows import SqlEntryUowRepository, SqlEntryUowRepositoryUnitOfWork
from .sql_entries import SqlEntryUowCreator
from .sql_entries import SqlEntryUowV1Creator, SqlEntryUowV2Creator
from .sql_resources import SqlResourceRepository, SqlResourceUnitOfWork
63 changes: 52 additions & 11 deletions karp/lex_infrastructure/repositories/sql_entries.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
"""SQL repositories for entries."""
import inspect
import logging
import typing
from typing import Dict, List, Optional, Tuple
from uuid import UUID
from typing import Dict, List, Optional, Generic, TypeVar

import injector
import regex
import sqlalchemy as sa
from sqlalchemy import sql
from sqlalchemy.orm import sessionmaker
import logging
import ulid

from karp.foundation.value_objects import UniqueId
from karp.foundation.events import EventBus
Expand Down Expand Up @@ -190,7 +188,7 @@ def _save(self, entry: Entry):
{
'entry_by_entry_id': entry_by_entry_id,
'entry_by_entity_id': entry_by_entity_id,
'entry': entry.dict(),
'entry': entry.dict(),
}
)
raise RuntimeError(f'entry = {entry.dict()}')
Expand Down Expand Up @@ -389,6 +387,12 @@ def get_total_entries(self) -> int:
self.runtime_model.discarded).filter_by(discarded=False)
return query.count()

def num_entities(self) -> int:
self._check_has_session()
query = self._session.query(
self.runtime_model.discarded).filter_by(discarded=False)
return query.count()

def by_referenceable(self, filters: Optional[Dict] = None, **kwargs) -> List[Entry]:
self._check_has_session()
# query = self._session.query(self.runtime_model)
Expand Down Expand Up @@ -564,7 +568,7 @@ class SqlEntryUnitOfWork(
SqlUnitOfWork,
repositories.EntryUnitOfWork,
):
repository_type: str = 'sql_entries_v1'
repository_type: str = 'sql_entries_base'

def __init__(
self,
Expand All @@ -585,12 +589,15 @@ def _begin(self):
self._session = self.session_factory()
if self._entries is None:
self._entries = SqlEntryRepository.from_dict(
name=self.name,
name=self.table_name(),
resource_config=self.config,
session=self._session
)
return self

def table_name(self) -> str:
return self.name

@property
def repo(self) -> SqlEntryRepository:
if self._entries is None:
Expand All @@ -606,6 +613,20 @@ def collect_new_events(self) -> typing.Iterable:
return super().collect_new_events()
else:
return []


class SqlEntryUnitOfWorkV1(SqlEntryUnitOfWork):
repository_type: str = 'sql_entries_v1'


class SqlEntryUnitOfWorkV2(SqlEntryUnitOfWork):
repository_type: str = 'sql_entries_v2'

def table_name(self) -> str:
u = ulid.from_uuid(self.entity_id)
random_part = u.randomness().str
return f"{self.name}_{random_part}"

# ===== Value objects =====
# class SqlEntryRepositorySettings(EntryRepositorySettings):
# def __init__(self, *, table_name: str, config: Dict):
Expand All @@ -623,9 +644,11 @@ def collect_new_events(self) -> typing.Iterable:
# runtime_table_name, history_model, settings.config
# )
# return SqlEntryRepository(history_model, runtime_model, settings.config)
SqlEntryUowType = TypeVar('SqlEntryUowType', bound=SqlEntryUnitOfWork)

class SqlEntryUowCreator:
repository_type: str = SqlEntryUnitOfWork.repository_type

class SqlEntryUowCreator(Generic[SqlEntryUowType]):
repository_type: str = "repository_type"

@injector.inject
def __init__(
Expand All @@ -646,9 +669,9 @@ def __call__(
user: str,
message: str,
timestamp: float,
) -> SqlEntryUnitOfWork:
) -> SqlEntryUowType:
if entity_id not in self.cache:
self.cache[entity_id] = SqlEntryUnitOfWork(
self.cache[entity_id] = self._create_uow(
entity_id=entity_id,
name=name,
config=config,
Expand All @@ -660,3 +683,21 @@ def __call__(
event_bus=self.event_bus,
)
return self.cache[entity_id]


class SqlEntryUowV1Creator(
SqlEntryUowCreator[SqlEntryUnitOfWorkV1]
):
repository_type: str = "sql_entries_v1"

def _create_uow(self, **kwargs) -> SqlEntryUnitOfWorkV1:
return SqlEntryUnitOfWorkV1(**kwargs)


class SqlEntryUowV2Creator(
SqlEntryUowCreator[SqlEntryUnitOfWorkV2]
):
repository_type: str = "sql_entries_v2"

def _create_uow(self, **kwargs) -> SqlEntryUnitOfWorkV2:
return SqlEntryUnitOfWorkV2(**kwargs)
22 changes: 22 additions & 0 deletions karp/lex_infrastructure/repositories/sql_entry_uows.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from typing import Optional

import sqlalchemy as sa
from sqlalchemy import sql
from sqlalchemy import orm as sa_orm

Expand Down Expand Up @@ -53,6 +54,27 @@ def _by_id(self, id_: UniqueId, **kwargs) -> Optional[EntryUnitOfWork]:
return self._row_to_entity(row)
return None

def num_entities(self) -> int:
self._check_has_session()
subq = (
self._session.query(
EntryUowModel.entity_id,
sa.func.max(EntryUowModel.last_modified).label("maxdate"),
)
.group_by(EntryUowModel.entity_id)
.subquery("t2")
)
query = self._session.query(EntryUowModel).join(
subq,
db.and_(
EntryUowModel.entity_id == subq.c.entity_id,
EntryUowModel.last_modified == subq.c.maxdate,
EntryUowModel.discarded == False,
),
)

return query.count()

def _row_to_entity(self, row_proxy) -> EntryUnitOfWork:
return self.entry_uow_factory.create(
repository_type=row_proxy.type,
Expand Down
Loading