Skip to content

Commit

Permalink
feat: DbCollection(TableData, TableColumns, DbConnection)
Browse files Browse the repository at this point in the history
  • Loading branch information
smotornyuk committed Jan 27, 2024
1 parent 42e547f commit 48a1b24
Show file tree
Hide file tree
Showing 18 changed files with 756 additions and 506 deletions.
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,23 @@ Base classes for viewing data series from CKAN.
* [Serializer service](#serializer-service)
* [Columns service](#columns-service)
* [Filters service](#filters-service)
* [Service implementations and usage examples](#service-implementations-and-usage-examples)
* [Core classes and usage examples](#core-classes-and-usage-examples)
* [Collection](#collection)
* [DbCollection](#dbcollection)
* [Data](#data)
* [StaticData](#staticdata)
* [BaseModelData](#basemodeldata)
* [StatementModelData](#statementmodeldata)
* [BaseSaData](#basesadata)
* [StatementSaData](#statementsadata)
* [UnionSaData](#unionsadata)
* [ModelData](#modeldata)
* [UnionModelData](#unionmodeldata)
* [TableData](#tabledata)
* [ApiData](#apidata)
* [ApiSearchData](#apisearchdata)
* [ApiListData](#apilistdata)
* [Pager](#pager)
* [ClassicPager](#classicpager)
* [Columns](#columns)
* [TableColumns](#tablecolumns)
* [Filters](#filters)
* [Serializer](#serializer)
* [CsvSerializer](#csvserializer)
Expand Down Expand Up @@ -663,7 +667,7 @@ ckanext-admin-panel defines allowed actions (remove, restore, hide) for content
and creates custom templates that are referring these actions.


### Service implementations and usage examples
### Core classes and usage examples

TBA

Expand All @@ -673,16 +677,16 @@ TBA
#### StaticData
TBA

#### BaseModelData
#### BaseSaData
TBA

#### StatementModelData
#### StatementSaData
TBA

#### ModelData
#### UnionSaData
TBA

#### UnionModelData
#### ModelData
TBA

#### ApiData
Expand Down
16 changes: 15 additions & 1 deletion ckanext/collection/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,24 @@ def configure(self, config_: CKANConfig):

def get_collection_factories(self) -> dict[str, CollectionFactory]:
if tk.config["debug"]:
from .utils import CollectionExplorerCollection
from ckan import model

from .utils import (
CollectionExplorerCollection,
DbCollection,
HtmxTableSerializer,
)

return {
"collection-explorer": CollectionExplorerCollection,
"core-db-explorer": lambda n, p: DbCollection(
n,
p,
db_connection_settings={"engine": model.meta.engine},
columns_settings={"table": "activity"},
data_settings={"table": "activity"},
serializer_factory=HtmxTableSerializer,
),
}

return {}
19 changes: 12 additions & 7 deletions ckanext/collection/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from ckanext.collection.types import (
BaseCollection,
CollectionFactory,
TDataCollection,
Service,
TDataCollection,
)

T = TypeVar("T")
Expand Down Expand Up @@ -146,11 +146,12 @@ def _gather_settings(self, kwargs: dict[str, Any]):
type(self),
lambda attr: isinstance(attr, _InitAttr),
):
attr: _InitAttr
if k in kwargs:
setattr(self, k, kwargs.pop(k))

else:
setattr(self, k, attr.get_default(self))
setattr(self, k, attr.get_default(self, k))


@dataclasses.dataclass
Expand All @@ -162,14 +163,18 @@ def __post_init__(self):
if self.default is not self.default_factory:
return

msg = "Either `default` or `default_factory` must be provided"
raise ValueError(msg)
def get_default(self, obj: Any, name: str):
if self.default is not SENTINEL:
return self.default

def get_default(self, obj: Any):
if self.default is SENTINEL:
if self.default_factory is not SENTINEL:
return self.default_factory(obj)

return self.default
msg = (
f"__init__() of {obj.__class__.__name__} missing "
+ f"1 required keyword-only argument: '{name}'"
)
raise TypeError(msg)


def configurable_attribute(
Expand Down
31 changes: 25 additions & 6 deletions ckanext/collection/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
from collections.abc import Sized
from typing import Any, Callable, Generic, Iterable, Literal, Sequence, Union

import sqlalchemy as sa
from sqlalchemy.engine import Engine
from typing_extensions import NotRequired, TypeAlias, TypedDict, TypeVar

CollectionFactory: TypeAlias = "Callable[[str, dict[str, Any]], BaseCollection[Any]]"
TDataCollection = TypeVar("TDataCollection", bound="BaseCollection[Any]")
TDbCollection = TypeVar("TDbCollection", bound="BaseDbCollection[Any]")
TFilterOptions = TypeVar("TFilterOptions")
TData = TypeVar("TData")

Expand Down Expand Up @@ -121,6 +124,17 @@ def dictize_row(self, row: Any) -> dict[str, Any]:
...


class BaseFilters(abc.ABC, Service):
"""Declaration of filters properties."""

filters: Sequence[Filter[Any]]
actions: Sequence[Filter[Any]]

@property
def service_name(self):
return "filters"


class BaseCollection(abc.ABC, Iterable[TData]):
"""Declaration of collection properties."""

Expand All @@ -134,15 +148,20 @@ class BaseCollection(abc.ABC, Iterable[TData]):
serializer: BaseSerializer


class BaseFilters(abc.ABC, Service):
"""Declaration of filters properties."""

filters: Sequence[Filter[Any]]
actions: Sequence[Filter[Any]]
class BaseDbConnection(abc.ABC, Service):
engine: Engine

@property
def service_name(self):
return "filters"
return "db_connection"

@property
def inspector(self):
return sa.inspect(self.engine)


class BaseDbCollection(BaseCollection[TData]):
db_connection: BaseDbConnection


class Filter(TypedDict, Generic[TFilterOptions]):
Expand Down
7 changes: 7 additions & 0 deletions ckanext/collection/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
ApiSearchCollection,
Collection,
CollectionExplorerCollection,
DbCollection,
ModelCollection,
StaticCollection,
)
Expand All @@ -17,8 +18,10 @@
ModelData,
StatementModelData,
StaticData,
TableData,
UnionModelData,
)
from .db_connection import DbConnection, UrlDbConnection
from .filters import Filters
from .pager import ClassicPager, Pager
from .serialize import (
Expand All @@ -33,6 +36,10 @@
)

__all__ = [
"DbCollection",
"TableData",
"DbConnection",
"UrlDbConnection",
"ApiCollection",
"ApiData",
"ApiListCollection",
Expand Down
24 changes: 24 additions & 0 deletions ckanext/collection/utils/collection/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from __future__ import annotations

from ckanext.collection import types
from ckanext.collection.utils.data import StaticData

from .api import ApiCollection, ApiListCollection, ApiSearchCollection
from .base import Collection
from .db import DbCollection
from .explorer import CollectionExplorerCollection
from .model import ModelCollection

__all__ = [
"Collection",
"DbCollection",
"ApiCollection",
"ApiSearchCollection",
"ApiListCollection",
"ModelCollection",
"CollectionExplorerCollection",
]


class StaticCollection(Collection[types.TData]):
DataFactory = StaticData
18 changes: 18 additions & 0 deletions ckanext/collection/utils/collection/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations

from ckanext.collection import types
from ckanext.collection.utils.data import ApiData, ApiListData, ApiSearchData

from .base import Collection


class ApiCollection(Collection[types.TData]):
DataFactory = ApiData


class ApiSearchCollection(ApiCollection[types.TData]):
DataFactory = ApiSearchData


class ApiListCollection(ApiCollection[types.TData]):
DataFactory = ApiListData
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
from __future__ import annotations

from typing import Any, Iterable, Iterator, overload
from typing import Any, Iterator, overload

from typing_extensions import Self

from ckan.authz import is_authorized_boolean

from ckanext.collection import shared, types

from .columns import Columns
from .data import ApiData, ApiListData, ApiSearchData, Data, ModelData, StaticData
from .filters import Filters
from .pager import ClassicPager, Pager
from .serialize import HtmlSerializer, Serializer
from ckanext.collection.utils.columns import Columns
from ckanext.collection.utils.data import Data
from ckanext.collection.utils.filters import Filters
from ckanext.collection.utils.pager import ClassicPager, Pager
from ckanext.collection.utils.serialize import Serializer


class Collection(types.BaseCollection[types.TData]):
Expand Down Expand Up @@ -178,43 +175,3 @@ def make_filters(self, **kwargs: Any) -> Filters[Self]:
def make_data(self, **kwargs: Any) -> Data[types.TData, Self]:
"""Return search filters."""
return self.DataFactory(self, **kwargs)


class StaticCollection(Collection[types.TData]):
DataFactory = StaticData


class ModelCollection(Collection[types.TData]):
DataFactory = ModelData


class ApiCollection(Collection[types.TData]):
DataFactory = ApiData


class ApiSearchCollection(ApiCollection[types.TData]):
DataFactory = ApiSearchData


class ApiListCollection(ApiCollection[types.TData]):
DataFactory = ApiListData


class CollectionExplorerCollection(Collection[str]):
class DataFactory(Data[str, "CollectionExplorerCollection"], shared.UserTrait):
def compute_data(self) -> Iterable[str]:
return [
str(name)
for name in shared.collection_registry.members
if name != self.attached.name
and is_authorized_boolean(
"collection_view_render",
{"user": self.user},
{"name": name},
)
]

class SerializerFactory(HtmlSerializer["CollectionExplorerCollection"]):
main_template: str = shared.configurable_attribute(
"collection/serialize/collection_explorer/main.html",
)
23 changes: 23 additions & 0 deletions ckanext/collection/utils/collection/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import annotations

from typing import Any

from typing_extensions import Self

from ckanext.collection import types
from ckanext.collection.utils.columns import TableColunns
from ckanext.collection.utils.data import TableData
from ckanext.collection.utils.db_connection import DbConnection

from .base import Collection


class DbCollection(Collection[types.TData], types.BaseDbCollection[types.TData]):
_service_names: tuple[str, ...] = ("db_connection",) + Collection._service_names
DbConnectionFactory: type[DbConnection[Self]] = DbConnection
DataFactory = TableData
ColumnsFactory = TableColunns

def make_db_connection(self, **kwargs: Any) -> DbConnection[Self]:
"""Return connection."""
return self.DbConnectionFactory(self, **kwargs)
41 changes: 41 additions & 0 deletions ckanext/collection/utils/collection/explorer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from __future__ import annotations

from typing import Iterable

from ckan.authz import is_authorized_boolean

from ckanext.collection import shared
from ckanext.collection.utils.data import Data
from ckanext.collection.utils.serialize import HtmlSerializer

from .base import Collection


class CollectionExplorerCollection(Collection[str]):
"""Collection of all registered collections.
It exists for debugging and serves as an example of non-canonical usage of
collections.
"""

class DataFactory(Data[str, "CollectionExplorerCollection"], shared.UserTrait):
static_names = shared.configurable_attribute(
default_factory=lambda self: map(str, shared.collection_registry.members),
)

def compute_data(self) -> Iterable[str]:
return [
name
for name in self.static_names
if name != self.attached.name
and is_authorized_boolean(
"collection_view_render",
{"user": self.user},
{"name": name},
)
]

class SerializerFactory(HtmlSerializer["CollectionExplorerCollection"]):
main_template: str = shared.configurable_attribute(
"collection/serialize/collection_explorer/main.html",
)
Loading

0 comments on commit 48a1b24

Please sign in to comment.