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

fix(usage): allow string to be used next to enums #197

Merged
merged 6 commits into from
Mar 22, 2024
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
6 changes: 3 additions & 3 deletions src/_example/django/django_demo/app/forest/customer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ async def _get_customer_fullname_values(records: List[RecordsDataAlias], context
return [f"{record['first_name']} - {record['last_name']}" for record in records]

return ComputedDefinition(
column_type=PrimitiveType.STRING,
column_type="String",
dependencies=["first_name", "last_name"],
get_values=_get_customer_fullname_values,
)
Expand Down Expand Up @@ -210,7 +210,7 @@ async def export_customers_json(context: ActionContextBulk, result_builder: Resu


export_json_action_dict: ActionDict = {
"scope": ActionsScope.BULK,
"scope": "bulk",
"generate_file": True,
"execute": export_customers_json,
}
Expand Down Expand Up @@ -256,7 +256,7 @@ async def age_operation_execute(context: ActionContextSingle, result_builder: Re
"enum_values": ["+", "-"],
},
{
"type": ActionFieldType.NUMBER,
"type": "Number",
"label": "Value",
"default_value": 0,
},
Expand Down
8 changes: 4 additions & 4 deletions src/_example/django/django_demo/app/forest_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def customize_forest(agent: DjangoAgent):
"postal_code",
{
"schema": {
"codePostal": PrimitiveType.STRING,
"codePostal": "String",
"codeCommune": PrimitiveType.STRING,
"nomCommune": PrimitiveType.STRING,
"libelleAcheminement": PrimitiveType.STRING,
Expand All @@ -91,7 +91,7 @@ def customize_forest(agent: DjangoAgent):
get_values=lambda records, context: [rec["order"]["customer_id"] for rec in records],
),
).emulate_field_operator(
"customer_id", Operator.IN
"customer_id", "in"
).replace_field_writing(
"name", cart_update_name
).add_segment(
Expand Down Expand Up @@ -128,7 +128,7 @@ def customize_forest(agent: DjangoAgent):
).replace_field_operator(
# custom operators for computed fields
"full_name",
Operator.EQUAL,
"equal",
full_name_equal,
).replace_field_operator(
"full_name", Operator.IN, full_name_in
Expand Down Expand Up @@ -203,7 +203,7 @@ def customize_forest(agent: DjangoAgent):
).add_field_validation(
# validation
"amount",
Operator.GREATER_THAN,
"greater_than",
0,
).add_field(
# # computed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@
from forestadmin.datasource_toolkit.decorators.write.write_replace.write_replace_collection import (
WriteReplaceCollection,
)
from forestadmin.datasource_toolkit.interfaces.fields import Column, FieldType, Operator, PrimitiveType
from forestadmin.datasource_toolkit.interfaces.fields import (
LITERAL_OPERATORS,
Column,
FieldType,
Operator,
PrimitiveType,
)
from forestadmin.datasource_toolkit.interfaces.models.collections import CollectionSchema
from forestadmin.datasource_toolkit.interfaces.query.sort import PlainSortClause
from forestadmin.datasource_toolkit.plugins.add_external_relation import AddExternalRelation, ExternalRelationDefinition
Expand Down Expand Up @@ -233,7 +239,7 @@ async def _remove_field():
return self

# # validation
def add_field_validation(self, name: str, operator: Operator, value: Any) -> Self:
def add_field_validation(self, name: str, operator: Union[Operator, LITERAL_OPERATORS], value: Any) -> Self:
"""Add a new validator to the edition form of a given field

Args:
Expand All @@ -251,13 +257,15 @@ def add_field_validation(self, name: str, operator: Operator, value: Any) -> Sel
async def _add_field_validation():
cast(
ValidationCollectionDecorator, self.stack.validation.get_collection(self.collection_name)
).add_validation(name, {"operator": operator, "value": value})
).add_validation(name, {"operator": Operator(operator), "value": value})

self.stack.queue_customization(_add_field_validation)
return self

# # operators
def replace_field_operator(self, name: str, operator: Operator, replacer: OperatorDefinition) -> Self:
def replace_field_operator(
self, name: str, operator: Union[Operator, LITERAL_OPERATORS], replacer: OperatorDefinition
) -> Self:
"""Replace an implementation for a specific operator on a specific field.
The operator replacement will be done by the datasource.

Expand All @@ -282,12 +290,14 @@ async def _replace_field_operator():
collection = self.stack.early_op_emulate.get_collection(self.collection_name)
else:
collection = self.stack.late_op_emulate.get_collection(self.collection_name)
cast(OperatorsEmulateCollectionDecorator, collection).replace_field_operator(name, operator, replacer)
cast(OperatorsEmulateCollectionDecorator, collection).replace_field_operator(
name, Operator(operator), replacer
)

self.stack.queue_customization(_replace_field_operator)
return self

def emulate_field_operator(self, name: str, operator: Operator) -> Self:
def emulate_field_operator(self, name: str, operator: Union[Operator, LITERAL_OPERATORS]) -> Self:
"""Enable filtering on a specific field with a specific operator using emulation.
As for all the emulation method, the field filtering will be done in-memory.

Expand All @@ -307,7 +317,7 @@ async def _emulate_field_operator():
collection = self.stack.early_op_emulate.get_collection(self.collection_name)
else:
collection = self.stack.late_op_emulate.get_collection(self.collection_name)
cast(OperatorsEmulateCollectionDecorator, collection).emulate_field_operator(name, operator)
cast(OperatorsEmulateCollectionDecorator, collection).emulate_field_operator(name, Operator(operator))

self.stack.queue_customization(_emulate_field_operator)
return self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ def _refine_schema(self, sub_schema: CollectionSchema) -> CollectionSchema:
for field in action.get("form", []):
dynamics.append(field.is_dynamic)
actions_schema[name] = Action(
scope=action["scope"], generate_file=action.get("generate_file", False), static_form=not any(dynamics)
scope=ActionsScope(action["scope"]),
generate_file=action.get("generate_file", False),
static_form=not any(dynamics),
)
return {**sub_schema, "actions": actions_schema}

Expand All @@ -100,7 +102,7 @@ def _get_context(
ActionsScope.SINGLE: ActionContextSingle,
ActionsScope.BULK: ActionContextBulk,
ActionsScope.GLOBAL: ActionContext,
}[action["scope"]](
}[ActionsScope(action["scope"])](
cast(Collection, self), caller, form_values, filter_, used, changed_field # type: ignore
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from forestadmin.datasource_toolkit.decorators.action.context.single import ActionContextSingle
from forestadmin.datasource_toolkit.decorators.action.result_builder import ResultBuilder
from forestadmin.datasource_toolkit.decorators.action.types.fields import BaseDynamicField, PlainDynamicField
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult, ActionsScope
from forestadmin.datasource_toolkit.interfaces.actions import ActionResult, ActionScopeLiteral, ActionsScope
from typing_extensions import NotRequired, TypedDict

ActionExecute = Union[
Expand All @@ -25,7 +25,7 @@


class ActionDict(TypedDict):
scope: ActionsScope
scope: Union[ActionsScope, ActionScopeLiteral]
generate_file: NotRequired[bool]
execute: ActionExecute
form: NotRequired[List[Union[PlainDynamicField, BaseDynamicField]]]
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ async def to_action_field(

# enum
class PlainEnumDynamicField(PlainField):
type: Literal[ActionFieldType.ENUM]
type: Literal[ActionFieldType.ENUM, "Enum"]
enum_values: ValueOrHandler[List[str]]
if_: NotRequired[ValueOrHandler[bool]]
value: NotRequired[ValueOrHandler[str]]
Expand Down Expand Up @@ -244,7 +244,7 @@ async def to_action_field(

# enum list
class PlainListEnumDynamicField(PlainField):
type: Literal[ActionFieldType.ENUM_LIST]
type: Literal[ActionFieldType.ENUM_LIST, "EnumList"]
enum_values: ValueOrHandler[List[str]]
value: NotRequired[ValueOrHandler[List[str]]]
default_value: NotRequired[ValueOrHandler[List[str]]]
Expand Down Expand Up @@ -293,7 +293,7 @@ async def to_action_field(

# boolean
class PlainBooleanDynamicField(PlainField):
type: Literal[ActionFieldType.BOOLEAN]
type: Literal[ActionFieldType.BOOLEAN, "Boolean"]
value: NotRequired[ValueOrHandler[bool]]
default_value: NotRequired[ValueOrHandler[bool]]

Expand All @@ -304,7 +304,7 @@ class BooleanDynamicField(BaseDynamicField[bool]):

# date only
class PlainDateOnlyDynamicField(PlainField):
type: Literal[ActionFieldType.DATE_ONLY]
type: Literal[ActionFieldType.DATE_ONLY, "Dateonly"]
value: NotRequired[ValueOrHandler[date]]
default_value: NotRequired[ValueOrHandler[date]]

Expand All @@ -315,7 +315,7 @@ class DateOnlyDynamicField(BaseDynamicField[date]):

# datetime
class PlainDateTimeDynamicField(PlainField):
type: Literal[ActionFieldType.DATE]
type: Literal[ActionFieldType.DATE, "Date"]
value: NotRequired[ValueOrHandler[datetime]]
default_value: NotRequired[ValueOrHandler[datetime]]

Expand All @@ -326,7 +326,7 @@ class DateTimeDynamicField(BaseDynamicField[datetime]):

# time
class PlainTimeDynamicField(PlainField):
type: Literal[ActionFieldType.TIME]
type: Literal[ActionFieldType.TIME, "Time"]
value: NotRequired[ValueOrHandler[str]]
default_value: NotRequired[ValueOrHandler[str]]

Expand All @@ -337,7 +337,7 @@ class TimeDynamicField(BaseDynamicField[str]):

# number
class PlainNumberDynamicField(PlainField):
type: Literal[ActionFieldType.NUMBER]
type: Literal[ActionFieldType.NUMBER, "Number"]
value: NotRequired[ValueOrHandler[Union[int, float]]]
default_value: NotRequired[ValueOrHandler[Union[int, float]]]

Expand All @@ -348,7 +348,7 @@ class NumberDynamicField(BaseDynamicField[Union[int, float]]):

# number list
class PlainListNumberDynamicField(PlainField):
type: Literal[ActionFieldType.NUMBER_LIST]
type: Literal[ActionFieldType.NUMBER_LIST, "NumberList"]
value: NotRequired[ValueOrHandler[Union[int, float]]]
default_value: NotRequired[ValueOrHandler[Union[int, float]]]

Expand All @@ -359,7 +359,7 @@ class NumberListDynamicField(BaseDynamicField[List[Union[int, float]]]):

# string
class PlainStringDynamicField(PlainField):
type: Literal[ActionFieldType.STRING]
type: Literal[ActionFieldType.STRING, "String"]
value: NotRequired[ValueOrHandler[str]]
default_value: NotRequired[ValueOrHandler[str]]

Expand All @@ -370,7 +370,7 @@ class StringDynamicField(BaseDynamicField[str]):

# string list
class PlainStringListDynamicField(PlainField):
type: Literal[ActionFieldType.STRING_LIST]
type: Literal[ActionFieldType.STRING_LIST, "StringList"]
value: NotRequired[ValueOrHandler[List[str]]]
default_value: NotRequired[ValueOrHandler[List[str]]]

Expand All @@ -381,7 +381,7 @@ class StringListDynamicField(BaseDynamicField[str]):

# json
class PlainJsonDynamicField(PlainField):
type: Literal[ActionFieldType.JSON]
type: Literal[ActionFieldType.JSON, "Json"]
value: NotRequired[ValueOrHandler[str]]
default_value: NotRequired[ValueOrHandler[str]]

Expand All @@ -396,7 +396,7 @@ class FileDynamicField(BaseDynamicField[File]):


class PlainFileDynamicField(PlainField):
type: Literal[ActionFieldType.FILE]
type: Literal[ActionFieldType.FILE, "File"]
value: NotRequired[ValueOrHandler[File]]
default_value: NotRequired[ValueOrHandler[File]]

Expand All @@ -407,7 +407,7 @@ class FileListDynamicField(BaseDynamicField[File]):


class PlainFileListDynamicField(PlainField):
type: Literal[ActionFieldType.FILE_LIST]
type: Literal[ActionFieldType.FILE_LIST, "FileList"]
value: NotRequired[ValueOrHandler[List[File]]]
default_value: NotRequired[ValueOrHandler[List[File]]]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from forestadmin.datasource_toolkit.decorators.computed.helpers import compute_from_records, rewrite_fields
from forestadmin.datasource_toolkit.decorators.computed.types import ComputedDefinition
from forestadmin.datasource_toolkit.interfaces.collections import Collection
from forestadmin.datasource_toolkit.interfaces.fields import FieldType, RelationAlias
from forestadmin.datasource_toolkit.interfaces.fields import ColumnAlias, FieldType, PrimitiveType, RelationAlias
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.paginated import PaginatedFilter
Expand Down Expand Up @@ -44,7 +44,10 @@ def register_computed(self, name: str, computed: ComputedDefinition):
for field in computed["dependencies"]:
FieldValidator.validate(self.child_collection, field)

self._computeds[name] = computed
# cast
column_type = ComputedCollectionDecorator._cast_column_type(computed["column_type"])

self._computeds[name] = cast(ComputedDefinition, {**computed, "column_type": column_type})
self.mark_schema_as_dirty()

async def list(self, caller: User, _filter: PaginatedFilter, projection: Projection) -> List[RecordsDataAlias]:
Expand Down Expand Up @@ -80,3 +83,15 @@ def _refine_schema(self, sub_schema: CollectionSchema) -> CollectionSchema:
"validations": [],
}
return {**sub_schema, "fields": computed_fields_schema}

@staticmethod
def _cast_column_type(
column_type_input: ColumnAlias,
) -> Union[PrimitiveType, Dict[str, PrimitiveType], List[PrimitiveType], List[Dict[str, PrimitiveType]]]:
if isinstance(column_type_input, dict):
column_type = {k: ComputedCollectionDecorator._cast_column_type(t) for k, t in column_type_input.items()}
elif isinstance(column_type_input, list):
column_type = [ComputedCollectionDecorator._cast_column_type(column) for column in column_type_input]
else:
column_type = PrimitiveType(column_type_input)
return column_type # type:ignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from forestadmin.datasource_toolkit.decorators.operators_emulate.types import OperatorDefinition
from forestadmin.datasource_toolkit.exceptions import ForestException
from forestadmin.datasource_toolkit.interfaces.collections import Collection
from forestadmin.datasource_toolkit.interfaces.fields import FieldAlias, Operator, RelationAlias
from forestadmin.datasource_toolkit.interfaces.fields import LITERAL_OPERATORS, FieldAlias, Operator, RelationAlias
from forestadmin.datasource_toolkit.interfaces.models.collections import CollectionSchema, Datasource
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.factory import ConditionTreeFactory
from forestadmin.datasource_toolkit.interfaces.query.condition_tree.nodes.base import ConditionTree
Expand All @@ -21,13 +21,15 @@

class OperatorsEmulateCollectionDecorator(CollectionDecorator):
def __init__(self, collection: Collection, datasource: Datasource):
self._fields: Dict[str, Dict[str, OperatorDefinition]] = {}
self._fields: Dict[str, Dict[Operator, Optional[OperatorDefinition]]] = {}
super().__init__(collection, datasource)

def emulate_field_operator(self, name: str, operator: Operator):
def emulate_field_operator(self, name: str, operator: Union[Operator, LITERAL_OPERATORS]):
self.replace_field_operator(name, operator, None)

def replace_field_operator(self, name: str, operator: Operator, replace_by: Optional[OperatorDefinition]):
def replace_field_operator(
self, name: str, operator: Union[Operator, LITERAL_OPERATORS], replace_by: Optional[OperatorDefinition]
):
# Check that the collection can actually support our rewriting
pks = SchemaUtils.get_primary_keys(self.child_collection.schema)
for pk in pks:
Expand All @@ -49,7 +51,7 @@ def replace_field_operator(self, name: str, operator: Operator, replace_by: Opti

if self._fields.get(name) is None:
self._fields[name] = dict()
self._fields[name][operator] = replace_by
self._fields[name][Operator(operator)] = replace_by
self.mark_schema_as_dirty()

def _refine_schema(self, sub_schema: CollectionSchema) -> CollectionSchema:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ def add_validation(self, name: str, validation: Validation):
if field is not None and field.get("is_read_only", False) is True:
raise ForestException("Cannot add validators on a readonly field")

# cast
validation["operator"] = Operator(validation["operator"])
if self.validations.get(name) is None:
self.validations[name] = []
self.validations[name].append(validation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class ActionsScope(enum.Enum):
GLOBAL = "global"


ActionScopeLiteral = Literal["single", "bulk", "global"]


@dataclass
class File:
mime_type: str
Expand Down
Loading
Loading