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

chore: typing for custom datasource #303

Merged
merged 8 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,6 @@ def param_builder(leaf: ConditionTreeLeaf):
request_params["params"].append(("q", filter_.search))
return request_params

async def create(self, caller: User, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
raise NotImplementedError("cannot create")

async def update(self, caller: User, filter_: Filter | None, patch: Dict[str, Any]) -> None:
raise NotImplementedError("cannot update")

async def delete(self, caller: User, filter_: Filter | None) -> None:
raise NotImplementedError("cannot delete")

async def list(self, caller: User, filter_: PaginatedFilter, projection: Projection) -> List[Dict[str, Any]]:
request = self._build_request(filter_.to_base_filter())
if filter_ and filter_.page:
Expand Down Expand Up @@ -83,9 +74,6 @@ async def aggregate(
limit,
)

def get_native_driver(self):
raise NotImplementedError("cannot delete")


class Comments(TypicodeCollection):
def __init__(self, datasource: Datasource[Self]):
Expand All @@ -94,7 +82,7 @@ def __init__(self, datasource: Datasource[Self]):
self.add_fields(
{
"id": {
"type": FieldType.COLUMN,
"type": "Column",
"is_primary_key": True,
"column_type": "Number",
"filter_operators": set([Operator.EQUAL]),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
from typing import Any, Dict, List, Optional
from __future__ import annotations

from copy import deepcopy
from typing import TYPE_CHECKING, Any, Dict, List, Optional

from forestadmin.agent_toolkit.utils.context import User
from forestadmin.datasource_toolkit.datasources import Datasource, DatasourceException
from forestadmin.datasource_toolkit.exceptions import ForestException
from forestadmin.datasource_toolkit.interfaces.actions import ActionFormElement, ActionResult
from forestadmin.datasource_toolkit.interfaces.chart import Chart
from forestadmin.datasource_toolkit.interfaces.collections import Collection as CollectionInterface
from forestadmin.datasource_toolkit.interfaces.fields import FieldAlias
from forestadmin.datasource_toolkit.interfaces.fields import (
COLUMN_DEFAULT,
FieldAlias,
FieldType,
Operator,
PrimitiveType,
)
from forestadmin.datasource_toolkit.interfaces.models.collections import CollectionSchema
from forestadmin.datasource_toolkit.interfaces.query.aggregation import AggregateResult, Aggregation
from forestadmin.datasource_toolkit.interfaces.query.filter.unpaginated import Filter
from forestadmin.datasource_toolkit.interfaces.records import RecordsDataAlias
from typing_extensions import Self

# from forestadmin.datasource_toolkit.decorators.action.types.actions import ActionDict
if TYPE_CHECKING:
from forestadmin.datasource_toolkit.decorators.action.types.actions import ActionDict


class CollectionException(DatasourceException):
Expand Down Expand Up @@ -48,15 +59,32 @@ def name(self) -> str:
def schema(self) -> CollectionSchema:
return self._schema

def add_action(self, name: str, action: "ActionDict"): # noqa:F821
def add_action(self, name: str, action: "ActionDict"):
if name in self.schema["actions"]:
raise CollectionException(f'Action "{name}" already defined in collection')
self.schema["actions"][name] = action

def add_field(self, name: str, field: FieldAlias):
if name in self.schema["fields"]:
raise CollectionException(f'Field "{name}" already defined in collection')
self.schema["fields"][name] = field

_field = {}
if field["type"] in ["Column", FieldType.COLUMN]:
# only column have optional fields
_field = deepcopy(COLUMN_DEFAULT)

_field.update(field)

# cast types from string to enums
if isinstance(_field["type"], str):
_field["type"] = FieldType(field["type"])
if "column_type" in _field and isinstance(_field["column_type"], str):
_field["column_type"] = PrimitiveType(field["column_type"])
_field["filter_operators"] = set(
[Operator(op) if isinstance(op, str) else op for op in _field.get("filter_operators", set())]
)

self.schema["fields"][name] = _field

def get_field(self, name: str):
try:
Expand Down Expand Up @@ -101,3 +129,20 @@ async def get_form(
async def render_chart(self, caller: User, name: str, record_id: List) -> Chart:
"""to render a chart"""
raise ForestException(f"Chart {name} is not implemented")

async def create(self, caller: User, data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
raise NotImplementedError()

async def update(self, caller: User, filter_: Optional[Filter], patch: Dict[str, Any]) -> None:
raise NotImplementedError()

async def delete(self, caller: User, filter_: Optional[Filter]) -> None:
raise NotImplementedError()

async def aggregate(
self, caller: User, filter_: Optional[Filter], aggregation: Aggregation, limit: Optional[int] = None
) -> List[AggregateResult]:
raise NotImplementedError()

def get_native_driver(self):
raise NotImplementedError()
Original file line number Diff line number Diff line change
Expand Up @@ -112,24 +112,16 @@ class PrimitiveType(enum.Enum):
"Binary",
]

LiteralManyToOne = Literal["ManyToOne"]
LiteralOneToOne = Literal["OneToOne"]
LiteralOneToMany = Literal["OneToMany"]
LiteralManyToMany = Literal["ManyToMany"]
LiteralPolymorphicManyToOne = Literal["PolymorphicManyToOne"]
LiteralPolymorphicOneToMany = Literal["PolymorphicOneToMany"]
LiteralPolymorphicOneToOne = Literal["PolymorphicOneToOne"]


class FieldType(enum.Enum):
COLUMN = "Column"
MANY_TO_ONE = LiteralManyToOne
ONE_TO_ONE = LiteralOneToOne
ONE_TO_MANY = LiteralOneToMany
MANY_TO_MANY = LiteralManyToMany
POLYMORPHIC_MANY_TO_ONE = LiteralPolymorphicManyToOne
POLYMORPHIC_ONE_TO_MANY = LiteralPolymorphicOneToMany
POLYMORPHIC_ONE_TO_ONE = LiteralPolymorphicOneToOne
MANY_TO_ONE = "ManyToOne"
ONE_TO_ONE = "OneToOne"
ONE_TO_MANY = "OneToMany"
MANY_TO_MANY = "ManyToMany"
POLYMORPHIC_MANY_TO_ONE = "PolymorphicManyToOne"
POLYMORPHIC_ONE_TO_MANY = "PolymorphicOneToMany"
POLYMORPHIC_ONE_TO_ONE = "PolymorphicOneToOne"


class Validation(TypedDict):
Expand All @@ -139,36 +131,47 @@ class Validation(TypedDict):

class Column(TypedDict):
column_type: "ColumnAlias"
filter_operators: Set[Operator]
default_value: Optional[Any]
enum_values: Optional[List[str]]
is_primary_key: bool
is_read_only: bool
is_sortable: bool
validations: List[Validation]
type: Literal[FieldType.COLUMN]
filter_operators: NotRequired[Set[Union[Operator, LITERAL_OPERATORS]]]
default_value: NotRequired[Optional[Any]]
enum_values: NotRequired[Optional[List[str]]]
is_primary_key: NotRequired[bool]
is_read_only: NotRequired[bool]
is_sortable: NotRequired[bool]
validations: NotRequired[List[Validation]]
type: Literal[FieldType.COLUMN, "Column"]


COLUMN_DEFAULT = {
"filter_operators": set(),
"default_value": None,
"enum_values": None,
"is_primary_key": False,
"is_read_only": False,
"is_sortable": False,
"validations": [],
}


class ManyToOne(TypedDict):
foreign_collection: str
foreign_key: str
foreign_key_target: str
type: Literal[FieldType.MANY_TO_ONE]
type: Literal[FieldType.MANY_TO_ONE, "ManyToOne"]


class PolymorphicManyToOne(TypedDict):
foreign_collections: List[str]
foreign_key: str
foreign_key_type_field: str
foreign_key_targets: Dict[str, str]
type: Literal[FieldType.POLYMORPHIC_MANY_TO_ONE]
type: Literal[FieldType.POLYMORPHIC_MANY_TO_ONE, "PolymorphicManyToOne"]


class PolymorphicOneToMany(TypedDict):
foreign_collection: str
origin_key: str
origin_key_target: str
type: Literal[FieldType.POLYMORPHIC_ONE_TO_MANY]
type: Literal[FieldType.POLYMORPHIC_ONE_TO_MANY, "PolymorphicOneToMany"]
origin_type_field: str
origin_type_value: str

Expand All @@ -177,7 +180,7 @@ class PolymorphicOneToOne(TypedDict):
foreign_collection: str
origin_key: str
origin_key_target: str
type: Literal[FieldType.POLYMORPHIC_ONE_TO_ONE]
type: Literal[FieldType.POLYMORPHIC_ONE_TO_ONE, "PolymorphicOneToOne"]
origin_type_field: str
origin_type_value: str

Expand All @@ -186,25 +189,38 @@ class OneToOne(TypedDict):
foreign_collection: str
origin_key: str
origin_key_target: str
type: Literal[FieldType.ONE_TO_ONE]
type: Literal[FieldType.ONE_TO_ONE, "OneToOne"]


class OneToMany(TypedDict):
foreign_collection: str
origin_key: str
origin_key_target: str
type: Literal[FieldType.ONE_TO_MANY]
type: Literal[FieldType.ONE_TO_MANY, "OneToMany"]


class ManyToMany(TypedDict):
through_collection: str
foreign_collection: str
foreign_key: str
foreign_key_target: str
foreign_relation: Optional[str]
foreign_relation: NotRequired[Optional[str]]
origin_key: str
origin_key_target: str
type: Literal[FieldType.MANY_TO_MANY]
type: Literal[FieldType.MANY_TO_MANY, "ManyToMany"]


class TypingHelper(TypedDict):
type: Literal[
"ManyToOne",
"OneToMany",
"OneToOne",
"ManyToMany",
"PolymorphicManyToOne",
"PolymorphicOneToMany",
"PolymorphicOneToOne",
"Column",
]


ColumnAlias = Union[PrimitiveType, PrimitiveTypeLiteral, Dict[str, "ColumnAlias"], List["ColumnAlias"]]
Expand All @@ -213,7 +229,7 @@ class ManyToMany(TypedDict):
ManyToMany, ManyToOne, OneToOne, OneToMany, PolymorphicManyToOne, PolymorphicOneToOne, PolymorphicOneToMany
]
PolyRelationAlias = Union[PolymorphicManyToOne, PolymorphicOneToOne, PolymorphicOneToMany]
FieldAlias = Union[Column, RelationAlias]
FieldAlias = Union[Column, RelationAlias, TypingHelper]


def is_column(field: "FieldAlias") -> TypeGuard[Column]:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, Dict, List, Optional, cast
from typing import Any, Dict, List, Optional, Union, cast

from forestadmin.datasource_toolkit.interfaces.query.filter.unpaginated import (
BaseFilter,
Expand All @@ -13,7 +13,7 @@

class PaginatedFilterComponent(FilterComponent, total=False):
page: Page
sort: Sort
sort: List[Union[PlainSortClause, Sort]]


class PlainPaginatedFilter(PlainFilter):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def setUpClass(cls) -> None:
"id": Column(column_type=PrimitiveType.NUMBER, is_primary_key=True, type=FieldType.COLUMN),
"first_name": Column(column_type=PrimitiveType.STRING, type=FieldType.COLUMN),
"last_name": Column(column_type=PrimitiveType.STRING, type=FieldType.COLUMN),
"book": OneToOne(origin_key="author_id", origin_key_target="id", foreign_collection="Book"),
"book": OneToOne(
origin_key="author_id", origin_key_target="id", foreign_collection="Book", type="OneToOne"
),
}
)
cls.collection_rating = Collection("Rating", cls.datasource)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ def setUpClass(cls) -> None:
cls.datasource: Datasource = Datasource()

Collection.__abstractmethods__ = set() # to instantiate abstract class
Collection.create = AsyncMock()
Collection.update = AsyncMock()
Collection.delete = AsyncMock()
Collection.aggregate = AsyncMock()
cls.collection_transaction = Collection("Transaction", cls.datasource)
cls.collection_transaction.add_fields(
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def setUpClass(cls) -> None:
"id": Column(column_type=PrimitiveType.NUMBER, is_primary_key=True, type=FieldType.COLUMN),
"first_name": Column(column_type=PrimitiveType.STRING, type=FieldType.COLUMN),
"last_name": Column(column_type=PrimitiveType.STRING, type=FieldType.COLUMN),
"books": OneToMany(origin_key="author_id", origin_key_target="id", foreign_collection="Book"),
"books": OneToMany(
origin_key="author_id", origin_key_target="id", foreign_collection="Book", type="OneToMany"
),
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class BaseTestOverrideCollectionDecorator(TestCase):
def setUpClass(cls) -> None:
cls.loop = asyncio.new_event_loop()
cls.datasource: Datasource = Datasource()
Collection.create = AsyncMock()
Collection.update = AsyncMock()
Collection.delete = AsyncMock()
Collection.aggregate = AsyncMock()
Collection.__abstractmethods__ = set() # to instantiate abstract class # type:ignore

cls.collection_transaction = Collection("Transaction", cls.datasource) # type:ignore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ def setUpClass(cls) -> None:
"id": Column(column_type=PrimitiveType.NUMBER, is_primary_key=True, type=FieldType.COLUMN),
"first_name": Column(column_type=PrimitiveType.STRING, type=FieldType.COLUMN),
"last_name": Column(column_type=PrimitiveType.STRING, type=FieldType.COLUMN),
"book": OneToOne(origin_key="author_id", origin_key_target="id", foreign_collection="Book"),
"book": OneToOne(
origin_key="author_id", origin_key_target="id", foreign_collection="Book", type=FieldType.ONE_TO_ONE
),
}
)
cls.datasource.add_collection(cls.collection_book)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,41 +12,21 @@
from forestadmin.datasource_toolkit.interfaces.fields import Operator, PrimitiveType
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.nodes.branch import Aggregator, ConditionTreeBranch
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.nodes.leaf import ConditionTreeLeaf
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_after_to_greater_than, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_after_x_hours_to_greater_than, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_before_to_less_than, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_before_x_hours_to_less_than, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_build_interval, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_compare_replacer, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_from_utc_iso_format, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_future_to_greater_than, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import _get_now # type: ignore
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_interval_replacer, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
_past_to_less_than, # type: ignore
)
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import _start_of # type: ignore
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.transforms.time import (
Frequency,
Interval,
_after_to_greater_than,
_after_x_hours_to_greater_than,
_before_to_less_than,
_before_x_hours_to_less_than,
_build_interval,
_compare_replacer,
_from_utc_iso_format,
_future_to_greater_than,
_get_now,
_interval_replacer,
_past_to_less_than,
_start_of,
compare,
format,
interval,
Expand Down
Loading