Skip to content

Commit

Permalink
Packaging and Typing Improvements (#8)
Browse files Browse the repository at this point in the history
* Package Metadata Fix for Typing and Version (#6)

* fix: Updated version in pyproject.toml

* feat: Added py.typed to enable typing stubs recognition.

* fix: Dynamic version works with packaging.

* fix: Package version now correct.

* Upgrade to Strict Type Checking (#7)

* fix: Moved to strict type checking and fixed all type errors.
  • Loading branch information
christopherpriebe authored Nov 6, 2024
1 parent 2864605 commit f79cbe5
Show file tree
Hide file tree
Showing 20 changed files with 174 additions and 24 deletions.
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ Issues = "https://github.com/actlab-fhy/FhY-core/issues"
[tool.setuptools.dynamic]
readme = {file = ["README.md"], content-type = "text/markdown"}
dependencies = {file = ["requirements.txt"]}
version = {attr = "fhy_core.__version__"}

[tool.setuptools]
package-dir = {"" = "src"}
Expand All @@ -41,6 +42,9 @@ package-dir = {"" = "src"}
where = ["src"]
exclude = ["tests"]

[tool.setuptools.package-data]
fhy_core = ["py.typed"]

[project.optional-dependencies]
dev = ["fhy_core[test,lint,type,docs]", "tox"]
test = ["pytest", "coverage", "pytest-xdist"]
Expand Down Expand Up @@ -73,6 +77,7 @@ exclude_also = [

[tool.mypy]
pretty = false
strict = true

[tool.pylint.main]
extension-pkg-whitelist = ["networkx"]
Expand Down
7 changes: 7 additions & 0 deletions src/fhy_core/constraint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
"""Constraint utility."""

__all__ = [
"Constraint",
"EquationConstraint",
"InSetConstraint",
"NotInSetConstraint",
]

from abc import ABC, abstractmethod
from typing import Any

Expand Down
20 changes: 20 additions & 0 deletions src/fhy_core/expression/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
"""General expression tree utility."""

__all__ = [
"BinaryExpression",
"BinaryOperation",
"Expression",
"IdentifierExpression",
"LiteralExpression",
"LiteralType",
"UnaryExpression",
"UnaryOperation",
"collect_identifiers",
"convert_expression_to_sympy_expression",
"convert_sympy_expression_to_expression",
"copy_expression",
"parse_expression",
"pformat_expression",
"simplify_expression",
"substitute_sympy_expression_variables",
"tokenize_expression",
]

from .core import (
BinaryExpression,
BinaryOperation,
Expand Down
18 changes: 18 additions & 0 deletions src/fhy_core/expression/core.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
"""General expression tree."""

__all__ = [
"Expression",
"UnaryOperation",
"UNARY_OPERATION_FUNCTION_NAMES",
"UNARY_FUNCTION_NAME_OPERATIONS",
"UNARY_OPERATION_SYMBOLS",
"UNARY_SYMBOL_OPERATIONS",
"UnaryExpression",
"BinaryOperation",
"BINARY_OPERATION_FUNCTION_NAMES",
"BINARY_FUNCTION_NAME_OPERATIONS",
"BINARY_OPERATION_SYMBOLS",
"BINARY_SYMBOL_OPERATIONS",
"BinaryExpression",
"IdentifierExpression",
"LiteralExpression",
]

from abc import ABC
from dataclasses import dataclass
from enum import Enum, auto
Expand Down
16 changes: 9 additions & 7 deletions src/fhy_core/expression/parser.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Parser from strings to expression trees."""

__all__ = ["parse_expression", "tokenize_expression"]

import re
from typing import Callable, TypeVar

Expand Down Expand Up @@ -126,23 +128,23 @@ def _equality(self) -> Expression:
def _comparison(self) -> Expression:
return self._binary_operation(_COMPARISON_SYMBOL_OPERATIONS, self._addition)

def _addition(self):
return self._binary_operation(_ADDITION_SYMBOL_OPERATIONS, self.multiplication)
def _addition(self) -> Expression:
return self._binary_operation(_ADDITION_SYMBOL_OPERATIONS, self._multiplication)

def multiplication(self):
def _multiplication(self) -> Expression:
return self._binary_operation(_MULTIPLICATION_SYMBOL_OPERATIONS, self._unary)

def _unary(self):
def _unary(self) -> Expression:
if self._match("-", "+", "!"):
op = self._get_previous_token()
right = self._unary()
return UnaryExpression(UNARY_SYMBOL_OPERATIONS[op], right)
return self._exponentiation()

def _exponentiation(self):
def _exponentiation(self) -> Expression:
return self._binary_operation(_EXPONENTIATION_SYMBOL_OPERATIONS, self._primary)

def _primary(self):
def _primary(self) -> Expression:
if self._match_number():
return LiteralExpression(self._get_previous_token())
elif self._match_identifier():
Expand Down Expand Up @@ -193,7 +195,7 @@ def _match_identifier(self) -> bool:
return True
return False

def _consume_token(self, token: str, error_message: str):
def _consume_token(self, token: str, error_message: str) -> str:
if self._peek_at_current_token() == token:
return self._advance_to_next_token()
else:
Expand Down
9 changes: 9 additions & 0 deletions src/fhy_core/expression/passes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
"""Analysis and transformation functions for expressions."""

__all__ = [
"collect_identifiers",
"copy_expression",
"convert_expression_to_sympy_expression",
"convert_sympy_expression_to_expression",
"simplify_expression",
"substitute_sympy_expression_variables",
]

from .basic import collect_identifiers, copy_expression
from .sympy import (
convert_expression_to_sympy_expression,
Expand Down
7 changes: 6 additions & 1 deletion src/fhy_core/expression/passes/basic.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
"""Basic expression passes."""

__all__ = [
"collect_identifiers",
"copy_expression",
]

from fhy_core.expression.core import (
Expression,
IdentifierExpression,
Expand All @@ -13,7 +18,7 @@ class IdentifierCollector(ExpressionVisitor):

_identifiers: set[Identifier]

def __init__(self):
def __init__(self) -> None:
self._identifiers = set()

@property
Expand Down
7 changes: 7 additions & 0 deletions src/fhy_core/expression/passes/sympy.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
"""Expression passes that interface with SymPy."""

__all__ = [
"convert_expression_to_sympy_expression",
"convert_sympy_expression_to_expression",
"simplify_expression",
"substitute_sympy_expression_variables",
]

import operator
from typing import Any, Callable

Expand Down
10 changes: 10 additions & 0 deletions src/fhy_core/expression/pprint.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Pretty-printer for expressions."""

__all__ = ["pformat_expression"]

from .core import (
BINARY_OPERATION_FUNCTION_NAMES,
BINARY_OPERATION_SYMBOLS,
Expand Down Expand Up @@ -27,6 +29,14 @@ def __init__(
self._is_id_shown = is_id_shown
self._is_printed_functional = is_printed_functional

def __call__(self, expression: Expression) -> str:
formatted_expression = super().__call__(expression)
if not isinstance(formatted_expression, str):
raise TypeError(
f"Invalid formatted expression type: {type(formatted_expression)}"
)
return formatted_expression

def visit_unary_expression(self, unary_expression: UnaryExpression) -> str:
if self._is_printed_functional:
return (
Expand Down
17 changes: 17 additions & 0 deletions src/fhy_core/expression/visitor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
"""Expression tree visitor and transformer."""

__all__ = [
"ExpressionBasePass",
"ExpressionVisitor",
"ExpressionTransformer",
]

from abc import ABC
from typing import Any

Expand Down Expand Up @@ -94,6 +100,9 @@ def visit_literal_expression(self, literal_expression: LiteralExpression) -> Any
class ExpressionVisitor(ExpressionBasePass, ABC):
"""Visitor for expression trees."""

def __call__(self, expression: Expression) -> None:
super().__call__(expression)

def visit_unary_expression(self, unary_expression: UnaryExpression) -> None:
self.visit(unary_expression.operand)

Expand All @@ -113,6 +122,14 @@ def visit_literal_expression(
class ExpressionTransformer(ExpressionBasePass, ABC):
"""Transformer for expression trees."""

def __call__(self, expression: Expression) -> Expression:
transformed_expression = super().__call__(expression)
if not isinstance(transformed_expression, Expression):
raise TypeError(
f"Invalid transformed expression type: {type(transformed_expression)}"
)
return transformed_expression

def visit_unary_expression(self, unary_expression: UnaryExpression) -> Expression:
new_expression = self.visit(unary_expression.operand)
return UnaryExpression(
Expand Down
2 changes: 2 additions & 0 deletions src/fhy_core/identifier.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Unique identifier for named FhY objects."""

__all__ = ["Identifier"]

from typing import Any


Expand Down
10 changes: 10 additions & 0 deletions src/fhy_core/param/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
"""Constrained parameters."""

__all__ = [
"CategoricalParam",
"IntParam",
"OrdinalParam",
"Param",
"PermParam",
"RealParam",
"NatParam",
]

from .core import CategoricalParam, IntParam, OrdinalParam, Param, PermParam, RealParam
from .fundamental import NatParam
27 changes: 19 additions & 8 deletions src/fhy_core/param/core.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
"""Core parameter structures."""

__all__ = [
"Param",
"RealParam",
"IntParam",
"OrdinalParam",
"CategoricalParam",
"PermParam",
]

from abc import ABC
from collections.abc import Sequence
from collections.abc import Hashable, Sequence
from typing import Any, Generic, TypeVar

from fhy_core.constraint import (
Expand All @@ -12,20 +21,22 @@
from fhy_core.expression import IdentifierExpression, LiteralExpression
from fhy_core.identifier import Identifier

H = TypeVar("H", bound=Hashable)


def _is_values_unique_in_sequence_without_set(values: Sequence) -> bool:
def _is_values_unique_in_sequence_without_set(values: Sequence[Any]) -> bool:
for i, value_1 in enumerate(values):
for value_2 in values[i + 1 :]:
if value_1 == value_2:
return False
return True


def _is_values_unique_in_sorted_sequence(values: Sequence) -> bool:
def _is_values_unique_in_sorted_sequence(values: Sequence[Any]) -> bool:
return all(values[i] != values[i + 1] for i in range(len(values) - 1))


def _is_values_unique_in_sequence_with_set(values: Sequence) -> bool:
def _is_values_unique_in_sequence_with_set(values: Sequence[H]) -> bool:
return len(values) == len(set(values))


Expand Down Expand Up @@ -161,23 +172,23 @@ def add_constraint(self, constraint: Constraint) -> None:
return super().add_constraint(constraint)


class CategoricalParam(Param[Any]):
class CategoricalParam(Param[H]):
"""Categorical parameter.
Note:
All values must be hashable.
"""

_categories: set[Any]
_categories: set[H]

def __init__(self, categories: Sequence[Any], name: Identifier | None = None):
def __init__(self, categories: Sequence[H], name: Identifier | None = None):
super().__init__(name)
if not _is_values_unique_in_sequence_with_set(categories):
raise ValueError("Values must be unique.")
self._categories = set(categories)

def set_value(self, value: Any) -> None:
def set_value(self, value: H) -> None:
if value not in self._categories:
raise ValueError("Value is not in the set of allowed categories.")
return super().set_value(value)
Expand Down
2 changes: 2 additions & 0 deletions src/fhy_core/param/fundamental.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Fundamental parameter classes."""

__all__ = ["NatParam"]

from fhy_core.constraint import EquationConstraint
from fhy_core.expression import (
BinaryExpression,
Expand Down
Empty file added src/fhy_core/py.typed
Empty file.
8 changes: 8 additions & 0 deletions src/fhy_core/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
"""General utilities."""

__all__ = [
"invert_dict",
"invert_frozen_dict",
"Lattice",
"PartiallyOrderedSet",
"Stack",
]

from .dict_utils import invert_dict, invert_frozen_dict
from .lattice import Lattice
from .poset import PartiallyOrderedSet
Expand Down
2 changes: 2 additions & 0 deletions src/fhy_core/utils/dict_utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Dictionary manipulation utilities."""

__all__ = ["invert_dict", "invert_frozen_dict"]

from typing import TypeVar

from frozendict import frozendict
Expand Down
4 changes: 3 additions & 1 deletion src/fhy_core/utils/lattice.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Lattice (order theory) utility."""

__all__ = ["Lattice"]

from typing import Generic, TypeVar

from .poset import PartiallyOrderedSet
Expand All @@ -12,7 +14,7 @@ class Lattice(Generic[T]):

_poset: PartiallyOrderedSet[T]

def __init__(self):
def __init__(self) -> None:
self._poset = PartiallyOrderedSet[T]()

def __contains__(self, element: T) -> bool:
Expand Down
Loading

0 comments on commit f79cbe5

Please sign in to comment.