# Path: formulaic/materializers/types/scoped_factor.py
# class ScopedFactor:
#     def __init__(self, factor, reduced=False):
#         self.factor = factor
#         self.reduced = reduced
# 
#     def __repr__(self):
#         return repr(self.factor) + ("-" if self.reduced else "")
# 
#     def __hash__(self):
#         return hash(repr(self))
# 
#     def __eq__(self, other):
#         if isinstance(other, ScopedFactor):
#             return self.factor == other.factor and self.reduced == other.reduced
#         return NotImplemented
# 
#     def __lt__(self, other):
#         if isinstance(other, ScopedFactor):
#             if self.factor == other.factor:
#                 return self.reduced > other.reduced
#             return self.factor < other.factor
#         return NotImplemented
# 
# Path: formulaic/materializers/types/scoped_term.py
# class ScopedTerm:
# 
#     __slots__ = ("factors", "scale")
# 
#     def __init__(self, factors, scale=None):
#         self.factors = tuple(sorted(factors))
#         self.scale = scale
# 
#     def __hash__(self):
#         return hash(self.factors)
# 
#     def __eq__(self, other):
#         if isinstance(other, ScopedTerm):
#             return self.factors == other.factors
#         return NotImplemented
# 
#     def __repr__(self):
#         factor_repr = (
#             ":".join(f.__repr__() for f in sorted(self.factors))
#             if self.factors
#             else "1"
#         )
#         if self.scale is not None and self.scale != 1:
#             return f"{self.scale}*{factor_repr}"
#         return factor_repr
# 
#     def copy(self, *, without_values=False):
#         factors = self.factors
#         if without_values:
#             factors = [
#                 ScopedFactor(
#                     factor=factor.factor.replace(values=None),
#                     reduced=factor.reduced,
#                 )
#                 for factor in factors
#             ]
#         return ScopedTerm(factors, scale=self.scale)
# 
# Path: formulaic/parser/types/factor.py
# class Factor:
#     """
#     Factors are the indivisable atomic unit that make up formulas.
# 
#     Each instance of `Factor` is a specification that is evaluable by a
#     materializer to generate concrete vector(s). `Factors` are multiplied
#     together into `Term`s, which in turn represent the output columns of model
#     matrices. Note that `Factor` instances are entirely abstract of data.
# 
#     Attributes:
#         expr: The (string) expression to be evaluated by the materializer.
#         eval_method: An `EvalMethod` enum instance indicating the mechanism to
#             be used to evaluate the expression (one of: unknown, literal, lookup
#             or python).
#         kind: The kind of data represented (one of: unknown, constant,
#             numerical, categorical).
#         metadata: An additional (optional) dictionary of metadata (currently
#             unused).
#     """
# 
#     class EvalMethod(Enum):
#         UNKNOWN = "unknown"
#         LITERAL = "literal"
#         LOOKUP = "lookup"
#         PYTHON = "python"
# 
#     class Kind(Enum):
#         UNKNOWN = "unknown"
#         CONSTANT = "constant"
#         NUMERICAL = "numerical"
#         CATEGORICAL = "categorical"
# 
#     __slots__ = ("expr", "_eval_method", "_kind", "metadata")
# 
#     def __init__(
#         self,
#         expr: str = "",
#         *,
#         eval_method: Optional[Union[str, EvalMethod]] = None,
#         kind: Optional[Union[str, Kind]] = None,
#         metadata: Optional[Dict] = None
#     ):
#         self.expr = expr
#         self.eval_method = eval_method
#         self.kind = kind
#         self.metadata = metadata or {}
# 
#     @property
#     def eval_method(self) -> EvalMethod:
#         return self._eval_method
# 
#     @eval_method.setter
#     def eval_method(self, eval_method):
#         self._eval_method = Factor.EvalMethod(eval_method or "unknown")
# 
#     @property
#     def kind(self) -> Kind:
#         return self._kind
# 
#     @kind.setter
#     def kind(self, kind):
#         self._kind = Factor.Kind(kind or "unknown")
# 
#     def __eq__(self, other):
#         if isinstance(other, str):
#             return self.expr == other
#         if isinstance(other, Factor):
#             return self.expr == other.expr
#         return NotImplemented
# 
#     def __hash__(self):
#         return self.expr.__hash__()
# 
#     def __lt__(self, other):
#         if isinstance(other, Factor):
#             return self.expr < other.expr
#         return NotImplemented
# 
#     def to_terms(self) -> Iterable[Term]:
#         """
#         Convert this `Factor` instance into a `Term` instance, and expose it as
#         a single-element iterable.
#         """
#         return {Term([self])}
# 
#     def __repr__(self):
#         return self.expr


import pytest
from formulaic.materializers.types import ScopedFactor, ScopedTerm
from formulaic.parser.types import Factor



class TestScopedTerm:
    @pytest.fixture
    def scoped_term(self):

# Path: formulaic/materializers/types/scoped_factor.py
# class ScopedFactor:
#     def __init__(self, factor, reduced=False):
#         self.factor = factor
#         self.reduced = reduced
# 
#     def __repr__(self):
#         return repr(self.factor) + ("-" if self.reduced else "")
# 
#     def __hash__(self):
#         return hash(repr(self))
# 
#     def __eq__(self, other):
#         if isinstance(other, ScopedFactor):
#             return self.factor == other.factor and self.reduced == other.reduced
#         return NotImplemented
# 
#     def __lt__(self, other):
#         if isinstance(other, ScopedFactor):
#             if self.factor == other.factor:
#                 return self.reduced > other.reduced
#             return self.factor < other.factor
#         return NotImplemented
# 
# Path: formulaic/materializers/types/scoped_term.py
# class ScopedTerm:
# 
#     __slots__ = ("factors", "scale")
# 
#     def __init__(self, factors, scale=None):
#         self.factors = tuple(sorted(factors))
#         self.scale = scale
# 
#     def __hash__(self):
#         return hash(self.factors)
# 
#     def __eq__(self, other):
#         if isinstance(other, ScopedTerm):
#             return self.factors == other.factors
#         return NotImplemented
# 
#     def __repr__(self):
#         factor_repr = (
#             ":".join(f.__repr__() for f in sorted(self.factors))
#             if self.factors
#             else "1"
#         )
#         if self.scale is not None and self.scale != 1:
#             return f"{self.scale}*{factor_repr}"
#         return factor_repr
# 
#     def copy(self, *, without_values=False):
#         factors = self.factors
#         if without_values:
#             factors = [
#                 ScopedFactor(
#                     factor=factor.factor.replace(values=None),
#                     reduced=factor.reduced,
#                 )
#                 for factor in factors
#             ]
#         return ScopedTerm(factors, scale=self.scale)
# 
# Path: formulaic/parser/types/factor.py
# class Factor:
#     """
#     Factors are the indivisable atomic unit that make up formulas.
# 
#     Each instance of `Factor` is a specification that is evaluable by a
#     materializer to generate concrete vector(s). `Factors` are multiplied
#     together into `Term`s, which in turn represent the output columns of model
#     matrices. Note that `Factor` instances are entirely abstract of data.
# 
#     Attributes:
#         expr: The (string) expression to be evaluated by the materializer.
#         eval_method: An `EvalMethod` enum instance indicating the mechanism to
#             be used to evaluate the expression (one of: unknown, literal, lookup
#             or python).
#         kind: The kind of data represented (one of: unknown, constant,
#             numerical, categorical).
#         metadata: An additional (optional) dictionary of metadata (currently
#             unused).
#     """
# 
#     class EvalMethod(Enum):
#         UNKNOWN = "unknown"
#         LITERAL = "literal"
#         LOOKUP = "lookup"
#         PYTHON = "python"
# 
#     class Kind(Enum):
#         UNKNOWN = "unknown"
#         CONSTANT = "constant"
#         NUMERICAL = "numerical"
#         CATEGORICAL = "categorical"
# 
#     __slots__ = ("expr", "_eval_method", "_kind", "metadata")
# 
#     def __init__(
#         self,
#         expr: str = "",
#         *,
#         eval_method: Optional[Union[str, EvalMethod]] = None,
#         kind: Optional[Union[str, Kind]] = None,
#         metadata: Optional[Dict] = None
#     ):
#         self.expr = expr
#         self.eval_method = eval_method
#         self.kind = kind
#         self.metadata = metadata or {}
# 
#     @property
#     def eval_method(self) -> EvalMethod:
#         return self._eval_method
# 
#     @eval_method.setter
#     def eval_method(self, eval_method):
#         self._eval_method = Factor.EvalMethod(eval_method or "unknown")
# 
#     @property
#     def kind(self) -> Kind:
#         return self._kind
# 
#     @kind.setter
#     def kind(self, kind):
#         self._kind = Factor.Kind(kind or "unknown")
# 
#     def __eq__(self, other):
#         if isinstance(other, str):
#             return self.expr == other
#         if isinstance(other, Factor):
#             return self.expr == other.expr
#         return NotImplemented
# 
#     def __hash__(self):
#         return self.expr.__hash__()
# 
#     def __lt__(self, other):
#         if isinstance(other, Factor):
#             return self.expr < other.expr
#         return NotImplemented
# 
#     def to_terms(self) -> Iterable[Term]:
#         """
#         Convert this `Factor` instance into a `Term` instance, and expose it as
#         a single-element iterable.
#         """
#         return {Term([self])}
# 
#     def __repr__(self):
#         return self.expr


# Path: tests/materializers/types/test_scoped_term.py
import pytest
from formulaic.materializers.types import ScopedFactor, ScopedTerm
from formulaic.parser.types import Factor




class TestScopedTerm:
    @pytest.fixture
    def scoped_term(self):

        return ScopedTerm([ScopedFactor(Factor("a")), ScopedFactor(Factor("b"))])