docs: add docstrings to frame classes
This commit is contained in:
@@ -22,6 +22,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Call:
|
class Call:
|
||||||
|
"""A column group-by method call, implements :class:`utils.MethodCall`"""
|
||||||
|
|
||||||
location: Location
|
location: Location
|
||||||
call_expr: p.Expr
|
call_expr: p.Expr
|
||||||
groupby: ColumnGroupBy
|
groupby: ColumnGroupBy
|
||||||
@@ -35,6 +37,8 @@ class Call:
|
|||||||
|
|
||||||
|
|
||||||
class ColumnGroupByMethodRegistry(MethodRegistry[Call]):
|
class ColumnGroupByMethodRegistry(MethodRegistry[Call]):
|
||||||
|
"""The method registry for column group-by types"""
|
||||||
|
|
||||||
NAMED_ARGS: dict[str, str] = {
|
NAMED_ARGS: dict[str, str] = {
|
||||||
"numeric_only": "bool",
|
"numeric_only": "bool",
|
||||||
"skipna": "bool",
|
"skipna": "bool",
|
||||||
@@ -49,6 +53,21 @@ class ColumnGroupByMethodRegistry(MethodRegistry[Call]):
|
|||||||
*,
|
*,
|
||||||
preserve_inner_type: bool = False,
|
preserve_inner_type: bool = False,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the result type of an aggregate method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call (Call): the call object
|
||||||
|
params (list[str | tuple[str, str, bool], optional): a list of extra
|
||||||
|
mixed parameters. The list can contain strings to include
|
||||||
|
parameters predefined in `NAMED_ARGS`, or tuples containing the
|
||||||
|
parameter's name, type and required flag. Defaults to [].
|
||||||
|
preserve_inner_type (bool, optional): If `True`, the result type
|
||||||
|
will preserve the column's inner type (e.g. for `min`/`max`),
|
||||||
|
otherwise the inner type is widened to `TopType`. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
real_params: list[Function.Parameter] = []
|
real_params: list[Function.Parameter] = []
|
||||||
for i, param in enumerate(params):
|
for i, param in enumerate(params):
|
||||||
match param:
|
match param:
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class ColumnManager:
|
class ColumnManager:
|
||||||
|
"""Helper class to handle methods and subscripts on column types"""
|
||||||
|
|
||||||
def __init__(self, typer: PythonTyper) -> None:
|
def __init__(self, typer: PythonTyper) -> None:
|
||||||
self.typer: PythonTyper = typer
|
self.typer: PythonTyper = typer
|
||||||
self.method_resolver: ColumnMethodRegistry = ColumnMethodRegistry(self.typer)
|
self.method_resolver: ColumnMethodRegistry = ColumnMethodRegistry(self.typer)
|
||||||
@@ -32,6 +34,20 @@ class ColumnManager:
|
|||||||
positional: list[TypedExpr],
|
positional: list[TypedExpr],
|
||||||
keywords: dict[str, TypedExpr],
|
keywords: dict[str, TypedExpr],
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the result type of a column's method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (str): the method name
|
||||||
|
location (Location): the call's location
|
||||||
|
call_expr (p.Expr): the call expression
|
||||||
|
column (ColumnType): the column type
|
||||||
|
column_expr (p.Expr): the column expression
|
||||||
|
positional (list[TypedExpr]): the list of positional arguments
|
||||||
|
keywords (dict[str, TypedExpr]): the map of keyword arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
call: Call = Call(
|
call: Call = Call(
|
||||||
location=location,
|
location=location,
|
||||||
call_expr=call_expr,
|
call_expr=call_expr,
|
||||||
@@ -52,6 +68,20 @@ class ColumnManager:
|
|||||||
positional: list[TypedExpr],
|
positional: list[TypedExpr],
|
||||||
keywords: dict[str, TypedExpr],
|
keywords: dict[str, TypedExpr],
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the result type of a column group-by's method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (str): the method name
|
||||||
|
location (Location): the call's location
|
||||||
|
call_expr (p.Expr): the call expression
|
||||||
|
groupby (ColumnGroupBy): the column group-by object
|
||||||
|
groupby_expr (p.Expr): the column group-by expression
|
||||||
|
positional (list[TypedExpr]): the list of positional arguments
|
||||||
|
keywords (dict[str, TypedExpr]): the map of keyword arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
call: GroupByCall = GroupByCall(
|
call: GroupByCall = GroupByCall(
|
||||||
location=location,
|
location=location,
|
||||||
call_expr=call_expr,
|
call_expr=call_expr,
|
||||||
@@ -63,6 +93,15 @@ class ColumnManager:
|
|||||||
return self.groupby_method_resolver.call(method, call)
|
return self.groupby_method_resolver.call(method, call)
|
||||||
|
|
||||||
def get_attribute(self, column: ColumnType, name: str) -> Optional[Type]:
|
def get_attribute(self, column: ColumnType, name: str) -> Optional[Type]:
|
||||||
|
"""Get the type of a column's attribute
|
||||||
|
|
||||||
|
Args:
|
||||||
|
column (ColumnType): the column type
|
||||||
|
name (str): the attribute's name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Type]: the attribute's type, or `None` if it doesn't exist
|
||||||
|
"""
|
||||||
types: TypesRegistry = self.typer.types
|
types: TypesRegistry = self.typer.types
|
||||||
match name:
|
match name:
|
||||||
case "ndim" | "size":
|
case "ndim" | "size":
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Call:
|
class Call:
|
||||||
|
"""A column method call, implements :class:`utils.MethodCall`"""
|
||||||
|
|
||||||
location: Location
|
location: Location
|
||||||
call_expr: p.Expr
|
call_expr: p.Expr
|
||||||
column: ColumnType
|
column: ColumnType
|
||||||
@@ -40,6 +42,8 @@ class Call:
|
|||||||
|
|
||||||
|
|
||||||
class ColumnMethodRegistry(MethodRegistry[Call]):
|
class ColumnMethodRegistry(MethodRegistry[Call]):
|
||||||
|
"""The method registry for column types"""
|
||||||
|
|
||||||
def _element_binary_op(self, call: Call, method: str) -> ColumnType:
|
def _element_binary_op(self, call: Call, method: str) -> ColumnType:
|
||||||
"""Compute the result of an element-wise binary operation
|
"""Compute the result of an element-wise binary operation
|
||||||
|
|
||||||
@@ -75,6 +79,18 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
return new_column
|
return new_column
|
||||||
|
|
||||||
def _element_wise(self, call: Call, method: str) -> Type:
|
def _element_wise(self, call: Call, method: str) -> Type:
|
||||||
|
"""Compute the result of an element-wise method call
|
||||||
|
|
||||||
|
If the call is valid, this method also generates an assertion to check
|
||||||
|
that both operands have the same length at runtime
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call (Call): the call object
|
||||||
|
method (str): the method's name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
# TODO: support add with scalar
|
# TODO: support add with scalar
|
||||||
|
|
||||||
# Build signature with new column type and generic operand
|
# Build signature with new column type and generic operand
|
||||||
@@ -170,6 +186,19 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
*,
|
*,
|
||||||
preserve_inner_type: bool = False,
|
preserve_inner_type: bool = False,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the result type of an aggregate method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call (Call): the call object
|
||||||
|
kwargs (list[Function.Parameter], optional): a list of extra
|
||||||
|
keyword-only parameters. Defaults to [].
|
||||||
|
preserve_inner_type (bool, optional): If `True`, the result type
|
||||||
|
will preserve the column's inner type (e.g. for `min`/`max`),
|
||||||
|
otherwise the inner type is widened to `TopType`. Defaults to False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
signature = Function(
|
signature = Function(
|
||||||
params=ParamSpec(
|
params=ParamSpec(
|
||||||
kw=[
|
kw=[
|
||||||
@@ -344,6 +373,14 @@ class ColumnMethodRegistry(MethodRegistry[Call]):
|
|||||||
return result.result
|
return result.result
|
||||||
|
|
||||||
def _assert_same_length(self, call_expr: p.Expr, column1: p.Expr, column2: p.Expr):
|
def _assert_same_length(self, call_expr: p.Expr, column1: p.Expr, column2: p.Expr):
|
||||||
|
"""Generate an assertion to check that two columns have the same length
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call_expr (p.Expr): the call expression, to insert the assertion
|
||||||
|
at the right place
|
||||||
|
column1 (p.Expr): the first column expression
|
||||||
|
column2 (p.Expr): the second column expression
|
||||||
|
"""
|
||||||
func_name: str = "__midas_column_same_length__"
|
func_name: str = "__midas_column_same_length__"
|
||||||
|
|
||||||
# Efficiently compute length
|
# Efficiently compute length
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Call:
|
class Call:
|
||||||
|
"""A frame group-by method call, implements :class:`utils.MethodCall`"""
|
||||||
|
|
||||||
location: Location
|
location: Location
|
||||||
call_expr: p.Expr
|
call_expr: p.Expr
|
||||||
groupby: FrameGroupBy
|
groupby: FrameGroupBy
|
||||||
@@ -34,14 +36,18 @@ class Call:
|
|||||||
|
|
||||||
|
|
||||||
class FrameGroupByMethodRegistry(MethodRegistry[Call]):
|
class FrameGroupByMethodRegistry(MethodRegistry[Call]):
|
||||||
NAMED_ARGS: dict[str, str] = {
|
"""The method registry for frame group-by types"""
|
||||||
"numeric_only": "bool",
|
|
||||||
"skipna": "bool",
|
|
||||||
"engine": "str",
|
|
||||||
"engine_kwargs": "dict",
|
|
||||||
}
|
|
||||||
|
|
||||||
def _aggregate(self, call: Call, method: str) -> Type:
|
def _aggregate(self, call: Call, method: str) -> Type:
|
||||||
|
"""Compute the result type of an aggregate method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call (Call): the call object
|
||||||
|
method (str): the method's name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
new_columns: list[DataFrameType.Column] = []
|
new_columns: list[DataFrameType.Column] = []
|
||||||
|
|
||||||
for column in call.groupby.frame.columns:
|
for column in call.groupby.frame.columns:
|
||||||
|
|||||||
@@ -24,10 +24,20 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
def is_list_of_literals(exprs: list[p.Expr]) -> TypeGuard[list[p.LiteralExpr]]:
|
def is_list_of_literals(exprs: list[p.Expr]) -> TypeGuard[list[p.LiteralExpr]]:
|
||||||
|
"""Check whether the given list only contains literal expressions
|
||||||
|
|
||||||
|
Args:
|
||||||
|
exprs (list[p.Expr]): the list to check
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TypeGuard[list[p.LiteralExpr]]: whether `exprs` only contains literal expressions
|
||||||
|
"""
|
||||||
return all(isinstance(expr, p.LiteralExpr) for expr in exprs)
|
return all(isinstance(expr, p.LiteralExpr) for expr in exprs)
|
||||||
|
|
||||||
|
|
||||||
class FrameManager:
|
class FrameManager:
|
||||||
|
"""Helper class to handle methods and subscripts on frame types"""
|
||||||
|
|
||||||
def __init__(self, typer: PythonTyper) -> None:
|
def __init__(self, typer: PythonTyper) -> None:
|
||||||
self.typer: PythonTyper = typer
|
self.typer: PythonTyper = typer
|
||||||
self.method_resolver: FrameMethodRegistry = FrameMethodRegistry(self.typer)
|
self.method_resolver: FrameMethodRegistry = FrameMethodRegistry(self.typer)
|
||||||
@@ -43,6 +53,18 @@ class FrameManager:
|
|||||||
index: p.Expr,
|
index: p.Expr,
|
||||||
value_type: Type,
|
value_type: Type,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the new frame type after assigning a value to an index
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reporter (FileReporter): the file reporter to use for diagnostics
|
||||||
|
location (Location): the assignment's location
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
index (p.Expr): the index expression
|
||||||
|
value_type (Type): the assigned value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the resulting frame type
|
||||||
|
"""
|
||||||
match index:
|
match index:
|
||||||
case p.LiteralExpr(value=str() as name):
|
case p.LiteralExpr(value=str() as name):
|
||||||
return self.assign_column(reporter, location, frame, name, value_type)
|
return self.assign_column(reporter, location, frame, name, value_type)
|
||||||
@@ -93,6 +115,18 @@ class FrameManager:
|
|||||||
name: str,
|
name: str,
|
||||||
type: Type,
|
type: Type,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the new frame type after assigning a single value to a column
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reporter (FileReporter): the file reporter to use for diagnostics
|
||||||
|
location (Location): the assignment's location
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
name (str): the column name
|
||||||
|
type (Type): the assigned value type
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the resulting frame type
|
||||||
|
"""
|
||||||
if not isinstance(type, ColumnType):
|
if not isinstance(type, ColumnType):
|
||||||
reporter.error(
|
reporter.error(
|
||||||
location,
|
location,
|
||||||
@@ -108,6 +142,17 @@ class FrameManager:
|
|||||||
frame: DataFrameType,
|
frame: DataFrameType,
|
||||||
index: p.Expr,
|
index: p.Expr,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the type of a subscript access
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reporter (FileReporter): the file reporter to use for diagnostics
|
||||||
|
location (Location): the subscript's location
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
index (p.Expr): the index expression
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the resulting type
|
||||||
|
"""
|
||||||
match index:
|
match index:
|
||||||
case p.LiteralExpr(value=str() as name):
|
case p.LiteralExpr(value=str() as name):
|
||||||
column: Optional[ColumnType] = FrameManager._get_column(frame, name)
|
column: Optional[ColumnType] = FrameManager._get_column(frame, name)
|
||||||
@@ -142,6 +187,17 @@ class FrameManager:
|
|||||||
groupby: FrameGroupBy,
|
groupby: FrameGroupBy,
|
||||||
index: p.Expr,
|
index: p.Expr,
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the type of a subscript access on a frame group-by object
|
||||||
|
|
||||||
|
Args:
|
||||||
|
reporter (FileReporter): the file reporter to use for diagnostics
|
||||||
|
location (Location): the subscript's location
|
||||||
|
groupby (FrameGroupBy): the group-by object
|
||||||
|
index (p.Expr): the index expression
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the resulting type
|
||||||
|
"""
|
||||||
result: Type = self.get(reporter, location, groupby.frame, index)
|
result: Type = self.get(reporter, location, groupby.frame, index)
|
||||||
match result:
|
match result:
|
||||||
case ColumnType():
|
case ColumnType():
|
||||||
@@ -159,6 +215,16 @@ class FrameManager:
|
|||||||
def _set_column(
|
def _set_column(
|
||||||
cls, frame: DataFrameType, name: str, column: ColumnType
|
cls, frame: DataFrameType, name: str, column: ColumnType
|
||||||
) -> DataFrameType:
|
) -> DataFrameType:
|
||||||
|
"""Set a frame's column to the given type
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
name (str): the column's name
|
||||||
|
column (ColumnType): the new column's type
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrameType: the new frame type
|
||||||
|
"""
|
||||||
new_columns: list[DataFrameType.Column] = []
|
new_columns: list[DataFrameType.Column] = []
|
||||||
index: int = len(frame.columns)
|
index: int = len(frame.columns)
|
||||||
replace: bool = False
|
replace: bool = False
|
||||||
@@ -185,12 +251,31 @@ class FrameManager:
|
|||||||
def _set_columns(
|
def _set_columns(
|
||||||
cls, frame: DataFrameType, names: list[str], columns: list[ColumnType]
|
cls, frame: DataFrameType, names: list[str], columns: list[ColumnType]
|
||||||
) -> DataFrameType:
|
) -> DataFrameType:
|
||||||
|
"""Set multiple columns of a frame to the given types
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
names (list[str]): the column names
|
||||||
|
columns (list[ColumnType]): the new column types
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
DataFrameType: the new frame type
|
||||||
|
"""
|
||||||
for name, col in zip(names, columns):
|
for name, col in zip(names, columns):
|
||||||
frame = cls._set_column(frame, name, col)
|
frame = cls._set_column(frame, name, col)
|
||||||
return frame
|
return frame
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_column(cls, frame: DataFrameType, name: str) -> Optional[ColumnType]:
|
def _get_column(cls, frame: DataFrameType, name: str) -> Optional[ColumnType]:
|
||||||
|
"""Get a column's type by name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
name (str): the column's name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[ColumnType]: the column's type, or `None` if it doesn't exist
|
||||||
|
"""
|
||||||
for col in frame.columns:
|
for col in frame.columns:
|
||||||
if col.name == name:
|
if col.name == name:
|
||||||
return col.type
|
return col.type
|
||||||
@@ -200,6 +285,15 @@ class FrameManager:
|
|||||||
def _get_columns(
|
def _get_columns(
|
||||||
cls, frame: DataFrameType, names: list[str]
|
cls, frame: DataFrameType, names: list[str]
|
||||||
) -> list[Optional[ColumnType]]:
|
) -> list[Optional[ColumnType]]:
|
||||||
|
"""Get multiple column types by name
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
names (list[str]): the column names
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[Optional[ColumnType]]: the column types (see :func:`_get_column`)
|
||||||
|
"""
|
||||||
return [cls._get_column(frame, name) for name in names]
|
return [cls._get_column(frame, name) for name in names]
|
||||||
|
|
||||||
def call(
|
def call(
|
||||||
@@ -212,6 +306,20 @@ class FrameManager:
|
|||||||
positional: list[TypedExpr],
|
positional: list[TypedExpr],
|
||||||
keywords: dict[str, TypedExpr],
|
keywords: dict[str, TypedExpr],
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the result type of a frame's method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (str): the method name
|
||||||
|
location (Location): the call's location
|
||||||
|
call_expr (p.Expr): the call expression
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
frame_expr (p.Expr): the frame expression
|
||||||
|
positional (list[TypedExpr]): the list of positional arguments
|
||||||
|
keywords (dict[str, TypedExpr]): the map of keyword arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
call: Call = Call(
|
call: Call = Call(
|
||||||
location=location,
|
location=location,
|
||||||
call_expr=call_expr,
|
call_expr=call_expr,
|
||||||
@@ -232,6 +340,20 @@ class FrameManager:
|
|||||||
positional: list[TypedExpr],
|
positional: list[TypedExpr],
|
||||||
keywords: dict[str, TypedExpr],
|
keywords: dict[str, TypedExpr],
|
||||||
) -> Type:
|
) -> Type:
|
||||||
|
"""Compute the result type of a frame group-by's method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (str): the method name
|
||||||
|
location (Location): the call's location
|
||||||
|
call_expr (p.Expr): the call expression
|
||||||
|
groupby (FrameGroupBy): the frame group-by object
|
||||||
|
groupby_expr (p.Expr): the frame group-by expression
|
||||||
|
positional (list[TypedExpr]): the list of positional arguments
|
||||||
|
keywords (dict[str, TypedExpr]): the map of keyword arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
call: GroupByCall = GroupByCall(
|
call: GroupByCall = GroupByCall(
|
||||||
location=location,
|
location=location,
|
||||||
call_expr=call_expr,
|
call_expr=call_expr,
|
||||||
@@ -243,6 +365,15 @@ class FrameManager:
|
|||||||
return self.groupby_method_resolver.call(method, call)
|
return self.groupby_method_resolver.call(method, call)
|
||||||
|
|
||||||
def get_attribute(self, frame: DataFrameType, name: str) -> Optional[Type]:
|
def get_attribute(self, frame: DataFrameType, name: str) -> Optional[Type]:
|
||||||
|
"""Get the type of a frame's attribute
|
||||||
|
|
||||||
|
Args:
|
||||||
|
frame (DataFrameType): the frame type
|
||||||
|
name (str): the attribute's name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Type]: the attribute's type, or `None` if it doesn't exist
|
||||||
|
"""
|
||||||
types: TypesRegistry = self.typer.types
|
types: TypesRegistry = self.typer.types
|
||||||
match name:
|
match name:
|
||||||
case "ndim" | "size":
|
case "ndim" | "size":
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
@dataclass(frozen=True, kw_only=True)
|
@dataclass(frozen=True, kw_only=True)
|
||||||
class Call:
|
class Call:
|
||||||
|
"""A frame method call, implements :class:`utils.MethodCall`"""
|
||||||
|
|
||||||
location: Location
|
location: Location
|
||||||
call_expr: p.Expr
|
call_expr: p.Expr
|
||||||
frame: DataFrameType
|
frame: DataFrameType
|
||||||
@@ -40,6 +42,8 @@ class Call:
|
|||||||
|
|
||||||
|
|
||||||
class FrameMethodRegistry(MethodRegistry[Call]):
|
class FrameMethodRegistry(MethodRegistry[Call]):
|
||||||
|
"""The method registry for frame types"""
|
||||||
|
|
||||||
def _get_method_result(
|
def _get_method_result(
|
||||||
self,
|
self,
|
||||||
call: Call,
|
call: Call,
|
||||||
@@ -148,6 +152,18 @@ class FrameMethodRegistry(MethodRegistry[Call]):
|
|||||||
return DataFrameType(columns=new_columns)
|
return DataFrameType(columns=new_columns)
|
||||||
|
|
||||||
def _element_wise(self, call: Call, method: str) -> Type:
|
def _element_wise(self, call: Call, method: str) -> Type:
|
||||||
|
"""Compute the result of an element-wise method call
|
||||||
|
|
||||||
|
If the call is valid, this method also generates an assertion to check
|
||||||
|
that both operands have the same length at runtime
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call (Call): the call object
|
||||||
|
method (str): the method's name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
# TODO: support scalar, sequence, Series, dict operand
|
# TODO: support scalar, sequence, Series, dict operand
|
||||||
# Build signature with new schema and generic operand
|
# Build signature with new schema and generic operand
|
||||||
signature = Function(
|
signature = Function(
|
||||||
@@ -231,6 +247,16 @@ class FrameMethodRegistry(MethodRegistry[Call]):
|
|||||||
return self._element_wise(call, "__eq__")
|
return self._element_wise(call, "__eq__")
|
||||||
|
|
||||||
def _aggregate(self, call: Call, kwargs: list[Function.Parameter] = []) -> Type:
|
def _aggregate(self, call: Call, kwargs: list[Function.Parameter] = []) -> Type:
|
||||||
|
"""Compute the result type of an aggregate method call
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call (Call): the call object
|
||||||
|
kwargs (list[Function.Parameter], optional): a list of extra
|
||||||
|
keyword-only parameters. Defaults to [].
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
with_axis = Function(
|
with_axis = Function(
|
||||||
params=ParamSpec(
|
params=ParamSpec(
|
||||||
kw=[
|
kw=[
|
||||||
@@ -425,6 +451,14 @@ class FrameMethodRegistry(MethodRegistry[Call]):
|
|||||||
return result.result
|
return result.result
|
||||||
|
|
||||||
def _assert_same_length(self, call_expr: p.Expr, frame1: p.Expr, frame2: p.Expr):
|
def _assert_same_length(self, call_expr: p.Expr, frame1: p.Expr, frame2: p.Expr):
|
||||||
|
"""Generate an assertion to check that two frames have the same length
|
||||||
|
|
||||||
|
Args:
|
||||||
|
call_expr (p.Expr): the call expression, to insert the assertion
|
||||||
|
at the right place
|
||||||
|
frame1 (p.Expr): the first frame expression
|
||||||
|
frame2 (p.Expr): the second frame expression
|
||||||
|
"""
|
||||||
func_name: str = "__midas_frame_same_length__"
|
func_name: str = "__midas_frame_same_length__"
|
||||||
|
|
||||||
# Efficiently compute length
|
# Efficiently compute length
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class _MethodRegistryMeta(type):
|
class _MethodRegistryMeta(type):
|
||||||
|
"""Meta-class for :class:`MethodRegistry`
|
||||||
|
|
||||||
|
Collects methods marked with the :func:`method` decorator into a dictionary
|
||||||
|
named `_methods` on the class itself
|
||||||
|
"""
|
||||||
|
|
||||||
_methods: dict[str, Callable[..., Type]] = {}
|
_methods: dict[str, Callable[..., Type]] = {}
|
||||||
|
|
||||||
def __new__(
|
def __new__(
|
||||||
@@ -42,6 +48,11 @@ class _MethodRegistryMeta(type):
|
|||||||
|
|
||||||
|
|
||||||
class MethodCall(Protocol):
|
class MethodCall(Protocol):
|
||||||
|
"""A method call object
|
||||||
|
|
||||||
|
Must have at least `location`, `call_expr` and `subject` properties
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location(self) -> Location: ...
|
def location(self) -> Location: ...
|
||||||
|
|
||||||
@@ -56,6 +67,8 @@ T = TypeVar("T", bound=MethodCall)
|
|||||||
|
|
||||||
|
|
||||||
class MethodRegistry(Generic[T], metaclass=_MethodRegistryMeta):
|
class MethodRegistry(Generic[T], metaclass=_MethodRegistryMeta):
|
||||||
|
"""A registry of methods"""
|
||||||
|
|
||||||
def __init__(self, typer: PythonTyper) -> None:
|
def __init__(self, typer: PythonTyper) -> None:
|
||||||
self.typer: PythonTyper = typer
|
self.typer: PythonTyper = typer
|
||||||
|
|
||||||
@@ -76,6 +89,15 @@ class MethodRegistry(Generic[T], metaclass=_MethodRegistryMeta):
|
|||||||
return self.typer.assertions
|
return self.typer.assertions
|
||||||
|
|
||||||
def call(self, method: str, call: T) -> Type:
|
def call(self, method: str, call: T) -> Type:
|
||||||
|
"""Compute the result type of a call to the given method
|
||||||
|
|
||||||
|
Args:
|
||||||
|
method (str): the method's name
|
||||||
|
call (T): the call
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Type: the result type
|
||||||
|
"""
|
||||||
func: Optional[Callable[[Self, T], Type]] = self._methods.get(method)
|
func: Optional[Callable[[Self, T], Type]] = self._methods.get(method)
|
||||||
if func is None:
|
if func is None:
|
||||||
self.reporter.warning(
|
self.reporter.warning(
|
||||||
@@ -90,6 +112,13 @@ Method = Callable[[_Self, T], Type]
|
|||||||
|
|
||||||
|
|
||||||
def method(*names: str) -> Callable[[Method[_Self, T]], Method[_Self, T]]:
|
def method(*names: str) -> Callable[[Method[_Self, T]], Method[_Self, T]]:
|
||||||
|
"""Simple decorator to mark a method as part of the registry
|
||||||
|
|
||||||
|
Args:
|
||||||
|
names (str): names by which the method can be called. If left empty, the
|
||||||
|
Python method's name will be used
|
||||||
|
"""
|
||||||
|
|
||||||
def wrapper(func: Method[_Self, T]) -> Method[_Self, T]:
|
def wrapper(func: Method[_Self, T]) -> Method[_Self, T]:
|
||||||
names_: tuple[str, ...] = names
|
names_: tuple[str, ...] = names
|
||||||
if len(names_) == 0:
|
if len(names_) == 0:
|
||||||
|
|||||||
Reference in New Issue
Block a user