diff --git a/changelog.d/pre-launch-cleanup.removed.md b/changelog.d/pre-launch-cleanup.removed.md new file mode 100644 index 00000000..73b95b51 --- /dev/null +++ b/changelog.d/pre-launch-cleanup.removed.md @@ -0,0 +1,6 @@ +Pre-launch cleanup — remove dead code and drop `plotly` from the core dependency set: + +- Delete `policyengine.tax_benefit_models.us` and `policyengine.tax_benefit_models.uk` module shims. Python resolves the package directory first, so the `.py` shims were always shadowed; worse, both attempted to re-export `general_policy_reform_analysis` which is not defined anywhere, making `from policyengine.tax_benefit_models.us import general_policy_reform_analysis` raise `ImportError` at runtime. +- Delete `_create_entity_output_model` plus the `PersonOutput` / `BenunitOutput` / `HouseholdEntityOutput` factory products in `policyengine.tax_benefit_models.uk.analysis` — built via `pydantic.create_model` but never referenced anywhere in the codebase. +- Delete `policyengine.core.DatasetVersion` (only consumer was an `Optional` field on `Dataset` that was never set, and the `policyengine.core` re-export). +- Move `plotly>=5.0.0` from the base install to a new `policyengine[plotting]` extra. Only `policyengine.utils.plotting` uses it, and that module is itself only used by the `examples/` scripts. The package now imports cleanly without `plotly`. diff --git a/pyproject.toml b/pyproject.toml index 67582060..72af3935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ dependencies = [ "pydantic>=2.0.0", "pandas>=2.0.0", "microdf_python>=1.2.1", - "plotly>=5.0.0", "requests>=2.31.0", "psutil>=5.9.0", "packaging>=23.0", @@ -34,6 +33,9 @@ dependencies = [ policyengine = "policyengine.cli:main" [project.optional-dependencies] +plotting = [ + "plotly>=5.0.0", +] uk = [ "policyengine_core>=3.25.0", "policyengine-uk==2.88.0", @@ -51,6 +53,7 @@ dev = [ "itables", "build", "jsonschema>=4.0.0", + "plotly>=5.0.0", "pytest-asyncio>=0.26.0", "ruff>=0.9.0", "policyengine_core>=3.25.0", diff --git a/src/policyengine/core/__init__.py b/src/policyengine/core/__init__.py index 8ff37aed..71ca0132 100644 --- a/src/policyengine/core/__init__.py +++ b/src/policyengine/core/__init__.py @@ -1,7 +1,6 @@ from .dataset import Dataset from .dataset import YearData as YearData from .dataset import map_to_entity as map_to_entity -from .dataset_version import DatasetVersion as DatasetVersion from .dynamic import Dynamic as Dynamic from .output import Output as Output from .output import OutputCollection as OutputCollection diff --git a/src/policyengine/core/dataset.py b/src/policyengine/core/dataset.py index 27f51d16..64f74eba 100644 --- a/src/policyengine/core/dataset.py +++ b/src/policyengine/core/dataset.py @@ -6,7 +6,6 @@ from microdf import MicroDataFrame from pydantic import BaseModel, ConfigDict, Field -from .dataset_version import DatasetVersion from .tax_benefit_model import TaxBenefitModel @@ -85,7 +84,6 @@ class MyDataset(Dataset): id: str = Field(default_factory=lambda: str(uuid4())) name: str description: str - dataset_version: Optional[DatasetVersion] = None filepath: str is_output_dataset: bool = False tax_benefit_model: Optional[TaxBenefitModel] = None diff --git a/src/policyengine/core/dataset_version.py b/src/policyengine/core/dataset_version.py deleted file mode 100644 index 711cd7d7..00000000 --- a/src/policyengine/core/dataset_version.py +++ /dev/null @@ -1,16 +0,0 @@ -from typing import TYPE_CHECKING -from uuid import uuid4 - -from pydantic import BaseModel, Field - -from .tax_benefit_model import TaxBenefitModel - -if TYPE_CHECKING: - from .dataset import Dataset - - -class DatasetVersion(BaseModel): - id: str = Field(default_factory=lambda: str(uuid4())) - dataset: "Dataset" - description: str - tax_benefit_model: TaxBenefitModel = None diff --git a/src/policyengine/tax_benefit_models/uk.py b/src/policyengine/tax_benefit_models/uk.py deleted file mode 100644 index 52abcb18..00000000 --- a/src/policyengine/tax_benefit_models/uk.py +++ /dev/null @@ -1,40 +0,0 @@ -"""PolicyEngine UK tax-benefit model - imports from uk/ module.""" - -from importlib.util import find_spec - -if find_spec("policyengine_uk") is not None: - from .uk import ( - PolicyEngineUK, - PolicyEngineUKDataset, - PolicyEngineUKLatest, - ProgrammeStatistics, - UKYearData, - create_datasets, - ensure_datasets, - general_policy_reform_analysis, - load_datasets, - managed_microsimulation, - uk_latest, - uk_model, - ) - - __all__ = [ - "UKYearData", - "PolicyEngineUKDataset", - "create_datasets", - "load_datasets", - "ensure_datasets", - "PolicyEngineUK", - "PolicyEngineUKLatest", - "managed_microsimulation", - "uk_model", - "uk_latest", - "general_policy_reform_analysis", - "ProgrammeStatistics", - ] - - # Rebuild models to resolve forward references - PolicyEngineUKDataset.model_rebuild() - PolicyEngineUKLatest.model_rebuild() -else: - __all__ = [] diff --git a/src/policyengine/tax_benefit_models/uk/analysis.py b/src/policyengine/tax_benefit_models/uk/analysis.py index 0a545b52..b05e21b0 100644 --- a/src/policyengine/tax_benefit_models/uk/analysis.py +++ b/src/policyengine/tax_benefit_models/uk/analysis.py @@ -6,7 +6,7 @@ import pandas as pd from microdf import MicroDataFrame -from pydantic import BaseModel, Field, create_model +from pydantic import BaseModel, Field from policyengine.core import OutputCollection, Simulation from policyengine.core.policy import Policy @@ -28,24 +28,6 @@ from .outputs import ProgrammeStatistics -def _create_entity_output_model(entity: str, variables: list[str]) -> type[BaseModel]: - """Create a dynamic Pydantic model for entity output variables.""" - fields = {var: (float, ...) for var in variables} - return create_model(f"{entity.title()}Output", **fields) - - -# Create output models dynamically from uk_latest.entity_variables -PersonOutput = _create_entity_output_model( - "person", uk_latest.entity_variables["person"] -) -BenunitOutput = _create_entity_output_model( - "benunit", uk_latest.entity_variables["benunit"] -) -HouseholdEntityOutput = _create_entity_output_model( - "household", uk_latest.entity_variables["household"] -) - - class UKHouseholdOutput(BaseModel): """Output from a UK household calculation with all entity data.""" diff --git a/src/policyengine/tax_benefit_models/us.py b/src/policyengine/tax_benefit_models/us.py deleted file mode 100644 index bbc29486..00000000 --- a/src/policyengine/tax_benefit_models/us.py +++ /dev/null @@ -1,40 +0,0 @@ -"""PolicyEngine US tax-benefit model - imports from us/ module.""" - -from importlib.util import find_spec - -if find_spec("policyengine_us") is not None: - from .us import ( - PolicyEngineUS, - PolicyEngineUSDataset, - PolicyEngineUSLatest, - ProgramStatistics, - USYearData, - create_datasets, - ensure_datasets, - general_policy_reform_analysis, - load_datasets, - managed_microsimulation, - us_latest, - us_model, - ) - - __all__ = [ - "USYearData", - "PolicyEngineUSDataset", - "create_datasets", - "load_datasets", - "ensure_datasets", - "PolicyEngineUS", - "PolicyEngineUSLatest", - "managed_microsimulation", - "us_model", - "us_latest", - "general_policy_reform_analysis", - "ProgramStatistics", - ] - - # Rebuild models to resolve forward references - PolicyEngineUSDataset.model_rebuild() - PolicyEngineUSLatest.model_rebuild() -else: - __all__ = [] diff --git a/src/policyengine/utils/__init__.py b/src/policyengine/utils/__init__.py index bf3cc681..bfbfe10b 100644 --- a/src/policyengine/utils/__init__.py +++ b/src/policyengine/utils/__init__.py @@ -1,7 +1,6 @@ from .dates import parse_safe_date as parse_safe_date +from .design import COLORS as COLORS from .parameter_labels import build_scale_lookup as build_scale_lookup from .parameter_labels import ( generate_label_for_parameter as generate_label_for_parameter, ) -from .plotting import COLORS as COLORS -from .plotting import format_fig as format_fig diff --git a/src/policyengine/utils/design.py b/src/policyengine/utils/design.py new file mode 100644 index 00000000..eda921a1 --- /dev/null +++ b/src/policyengine/utils/design.py @@ -0,0 +1,24 @@ +"""PolicyEngine brand colours and typography tokens. + +Lives outside ``plotting`` so consumers can import ``COLORS`` without +pulling plotly in. +""" + +COLORS = { + "primary": "#319795", # Teal + "primary_light": "#E6FFFA", + "primary_dark": "#1D4044", + "success": "#22C55E", # Green (positive changes) + "warning": "#FEC601", # Yellow (cautions) + "error": "#EF4444", # Red (negative changes) + "info": "#1890FF", # Blue (neutral info) + "gray_light": "#F2F4F7", + "gray": "#667085", + "gray_dark": "#101828", + "blue_secondary": "#026AA2", +} + +FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" +FONT_SIZE_LABEL = 12 +FONT_SIZE_DEFAULT = 14 +FONT_SIZE_TITLE = 16 diff --git a/src/policyengine/utils/plotting.py b/src/policyengine/utils/plotting.py index 2ca8e48c..15243e0e 100644 --- a/src/policyengine/utils/plotting.py +++ b/src/policyengine/utils/plotting.py @@ -1,29 +1,41 @@ -"""Plotting utilities for PolicyEngine visualisations.""" - -from typing import Optional - -import plotly.graph_objects as go - -# PolicyEngine brand colours -COLORS = { - "primary": "#319795", # Teal - "primary_light": "#E6FFFA", - "primary_dark": "#1D4044", - "success": "#22C55E", # Green (positive changes) - "warning": "#FEC601", # Yellow (cautions) - "error": "#EF4444", # Red (negative changes) - "info": "#1890FF", # Blue (neutral info) - "gray_light": "#F2F4F7", - "gray": "#667085", - "gray_dark": "#101828", - "blue_secondary": "#026AA2", -} - -# Typography -FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" -FONT_SIZE_LABEL = 12 -FONT_SIZE_DEFAULT = 14 -FONT_SIZE_TITLE = 16 +"""Plotting utilities for PolicyEngine visualisations. + +Requires plotly, which is installed via the ``[plotting]`` extra +(``pip install policyengine[plotting]``). Importing from this module +fails with a clear error when plotly is absent. Brand tokens +(``COLORS``, font constants) live in :mod:`policyengine.utils.design` +so they can be imported without plotly. +""" + +from typing import TYPE_CHECKING, Optional + +try: + import plotly.graph_objects as go +except ImportError as exc: # pragma: no cover + raise ImportError( + "policyengine.utils.plotting requires plotly. " + "Install with: pip install policyengine[plotting]" + ) from exc + +if TYPE_CHECKING: + import plotly.graph_objects as go # noqa: F401 + +from .design import ( + COLORS, + FONT_FAMILY, + FONT_SIZE_DEFAULT, + FONT_SIZE_LABEL, + FONT_SIZE_TITLE, +) + +__all__ = [ + "COLORS", + "FONT_FAMILY", + "FONT_SIZE_DEFAULT", + "FONT_SIZE_LABEL", + "FONT_SIZE_TITLE", + "format_fig", +] def format_fig(