diff --git a/doc/source/conf.py b/doc/source/conf.py index 1dcc2a42fb..8ce540f08e 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -90,7 +90,7 @@ full_name = os.path.split(file_to_copy)[-1] folder, file_name = full_name.split("_") if not file_name.endswith("ipynb"): - file_name = "_".join((folder, file_name)) + file_name = f"{folder}_{file_name}" out_dir = os.path.join(folder, "examples") if not os.path.exists(out_dir): os.makedirs(out_dir, exist_ok=True) diff --git a/linearmodels/__init__.py b/linearmodels/__init__.py index c7e9221b18..3a87a07961 100644 --- a/linearmodels/__init__.py +++ b/linearmodels/__init__.py @@ -56,10 +56,10 @@ from .system import IV3SLS, SUR, IVSystemGMM OLS = _OLS -WARN_ON_MISSING = os.environ.get("LINEARMODELS_WARN_ON_MISSING", "1") -WARN_ON_MISSING = False if WARN_ON_MISSING in ("", "0", "false", "False") else True -DROP_MISSING = os.environ.get("LINEARMODELS_DROP_MISSING", "1") -DROP_MISSING = False if DROP_MISSING in ("", "0", "false", "False") else True +_WARN_ON_MISSING = os.environ.get("LINEARMODELS_WARN_ON_MISSING", "1") +WARN_ON_MISSING = False if _WARN_ON_MISSING in ("", "0", "false", "False") else True +_DROP_MISSING = os.environ.get("LINEARMODELS_DROP_MISSING", "1") +DROP_MISSING = False if _DROP_MISSING in ("", "0", "false", "False") else True __all__ = [ "DROP_MISSING", diff --git a/linearmodels/conftest.py b/linearmodels/conftest.py index a2382a4f5c..1ead359b0c 100644 --- a/linearmodels/conftest.py +++ b/linearmodels/conftest.py @@ -20,7 +20,7 @@ logger.critical("Copy on Write testing enabled") -def pytest_configure(config): +def pytest_configure(config: pytest.Config) -> None: # Minimal config to simplify running tests from lm.test() config.addinivalue_line("markers", "example: mark a test as an example") config.addinivalue_line("markers", "slow: mark a test as slow") @@ -32,7 +32,7 @@ def pytest_configure(config): ) -def pytest_addoption(parser): +def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption("--skip-slow", action="store_true", help="skip slow tests") parser.addoption("--only-slow", action="store_true", help="run only slow tests") parser.addoption("--skip-smoke", action="store_true", help="skip smoke tests") @@ -40,7 +40,7 @@ def pytest_addoption(parser): parser.addoption("--skip-examples", action="store_true", help="skip examples tests") -def pytest_runtest_setup(item): +def pytest_runtest_setup(item: pytest.Item) -> None: if "slow" in item.keywords and item.config.getoption("--skip-slow"): pytest.skip("skipping due to --skip-slow") diff --git a/linearmodels/iv/absorbing.py b/linearmodels/iv/absorbing.py index 1c364e7750..9ce075118b 100644 --- a/linearmodels/iv/absorbing.py +++ b/linearmodels/iv/absorbing.py @@ -77,7 +77,7 @@ def __init__(self) -> None: self._hasher = hashlib.sha256() self._use_xxh64 = False - def reset(self): + def reset(self) -> None: if self._use_xxh64: assert isinstance(self._hasher, xxh64) self._hasher.reset() @@ -108,7 +108,7 @@ def lsmr_annihilate( use_cache: bool = True, x_hash: Hashable | None = None, **lsmr_options: ( - bool | float | str | linearmodels.typing.data.ArrayLike | None | dict[str, Any] + bool | float | str | linearmodels.typing.data.ArrayLike | dict[str, Any] | None ), ) -> linearmodels.typing.data.Float64Array: r""" @@ -149,7 +149,7 @@ def lsmr_annihilate( regressor_hash = x_hash if x_hash is not None else "" default_opts: dict[ str, - bool | float | str | linearmodels.typing.data.ArrayLike | None | dict[str, Any], + bool | float | str | linearmodels.typing.data.ArrayLike | dict[str, Any] | None, ] = {"atol": 1e-8, "btol": 1e-8, "show": False} assert lsmr_options is not None default_opts.update(lsmr_options) @@ -865,16 +865,17 @@ def _prepare_interactions(self) -> None: def _first_time_fit( self, use_cache: bool, - absorb_options: None | ( + absorb_options: ( dict[ str, bool | float | str | linearmodels.typing.data.ArrayLike - | None - | dict[str, Any], + | dict[str, Any] + | None, ] + | None ), method: str, ) -> None: @@ -988,16 +989,17 @@ def fit( cov_type: str = "robust", debiased: bool = False, method: str = "auto", - absorb_options: None | ( + absorb_options: ( dict[ str, bool | float | str | linearmodels.typing.data.ArrayLike - | None - | dict[str, Any], + | dict[str, Any] + | None, ] + | None ) = None, use_cache: bool = True, lsmr_options: dict[str, float | bool] | None = None, diff --git a/linearmodels/iv/results.py b/linearmodels/iv/results.py index 967c7cb957..e13d63092e 100644 --- a/linearmodels/iv/results.py +++ b/linearmodels/iv/results.py @@ -584,7 +584,7 @@ def predict( return out_df @property - def kappa(self) -> float: + def kappa(self) -> float | None: """k-class estimator value""" return self._kappa diff --git a/linearmodels/panel/data.py b/linearmodels/panel/data.py index 092a5be18b..14d78934f5 100644 --- a/linearmodels/panel/data.py +++ b/linearmodels/panel/data.py @@ -358,7 +358,7 @@ def entities(self) -> list[linearmodels.typing.Label]: return list(index.levels[0][index.codes[0]].unique()) @property - def entity_ids(self) -> linearmodels.typing.data.IntArray: + def entity_ids(self) -> linearmodels.typing.data.AnyIntArray: """ Get array containing entity group membership information @@ -371,7 +371,7 @@ def entity_ids(self) -> linearmodels.typing.data.IntArray: return np.asarray(index.codes[0])[:, None] @property - def time_ids(self) -> linearmodels.typing.data.IntArray: + def time_ids(self) -> linearmodels.typing.data.AnyIntArray: """ Get array containing time membership information diff --git a/linearmodels/shared/hypotheses.py b/linearmodels/shared/hypotheses.py index ddf3dc35cd..9659368b33 100644 --- a/linearmodels/shared/hypotheses.py +++ b/linearmodels/shared/hypotheses.py @@ -1,6 +1,7 @@ from __future__ import annotations from collections.abc import Mapping +from typing import Any from formulaic.utils.constraints import LinearConstraints import numpy as np @@ -46,6 +47,7 @@ def __init__( self.df = df self.df_denom = df_denom self._name = name + self.dist: Any if df_denom is None: self.dist = chi2(df) self.dist_name = f"chi2({df})" diff --git a/linearmodels/system/model.py b/linearmodels/system/model.py index 54707d1fb0..fe83027ac2 100644 --- a/linearmodels/system/model.py +++ b/linearmodels/system/model.py @@ -909,8 +909,11 @@ def _common_indiv_results( constant: bool, total_ss: float, *, - weight_est: None | ( - HomoskedasticWeightMatrix | HeteroskedasticWeightMatrix | KernelWeightMatrix + weight_est: ( + HomoskedasticWeightMatrix + | HeteroskedasticWeightMatrix + | KernelWeightMatrix + | None ) = None, ) -> AttrDict: loc = 0 diff --git a/linearmodels/tests/asset_pricing/test_linear_factor_gmm.py b/linearmodels/tests/asset_pricing/test_linear_factor_gmm.py index 4de4b90192..de309179b4 100644 --- a/linearmodels/tests/asset_pricing/test_linear_factor_gmm.py +++ b/linearmodels/tests/asset_pricing/test_linear_factor_gmm.py @@ -78,9 +78,10 @@ def test_linear_model_gmm_smoke_risk_free(data): mod = LinearFactorModelGMM(data.portfolios, data.factors, risk_free=True) res = mod.fit(cov_type="robust", disp=10) get_all(res) - str(res._cov_est) - res._cov_est.__repr__() - str(res._cov_est.config) + # Smoke tests + assert isinstance(str(res._cov_est), str) + assert isinstance(res._cov_est.__repr__(), str) + assert isinstance(str(res._cov_est.config), str) @pytest.mark.smoke @@ -88,9 +89,10 @@ def test_linear_model_gmm_kernel_smoke(data): mod = LinearFactorModelGMM(data.portfolios, data.factors) res = mod.fit(cov_type="kernel", disp=10) get_all(res) - str(res._cov_est) - res._cov_est.__repr__() - str(res._cov_est.config) + # Smoke tests + assert isinstance(str(res._cov_est), str) + assert isinstance(res._cov_est.__repr__(), str) + assert isinstance(str(res._cov_est.config), str) @pytest.mark.smoke diff --git a/linearmodels/tests/iv/test_model.py b/linearmodels/tests/iv/test_model.py index 70b3421edc..0412a9495d 100644 --- a/linearmodels/tests/iv/test_model.py +++ b/linearmodels/tests/iv/test_model.py @@ -394,10 +394,10 @@ def test_first_stage_summary(data): def test_gmm_str(data): mod = IVGMM(data.dep, data.exog, data.endog, data.instr) - str(mod.fit(cov_type="unadjusted")) - str(mod.fit(cov_type="robust")) - str(mod.fit(cov_type="clustered", clusters=data.clusters)) - str(mod.fit(cov_type="kernel")) + assert isinstance(str(mod.fit(cov_type="unadjusted")), str) + assert isinstance(str(mod.fit(cov_type="robust")), str) + assert isinstance(str(mod.fit(cov_type="clustered", clusters=data.clusters)), str) + assert isinstance(str(mod.fit(cov_type="kernel")), str) def test_gmm_cue_optimization_options(small_data): diff --git a/linearmodels/tests/iv/test_results.py b/linearmodels/tests/iv/test_results.py index 7ff0dc7a7d..20a068f6f4 100644 --- a/linearmodels/tests/iv/test_results.py +++ b/linearmodels/tests/iv/test_results.py @@ -34,7 +34,7 @@ def result_checker(res): _attr() else: assert isinstance(_attr, object) - str(_attr) + assert isinstance(str(_attr), str) def test_results(data, model): diff --git a/linearmodels/tests/system/results/execute-stata-3sls.py b/linearmodels/tests/system/results/execute-stata-3sls.py index 77645a38ac..cef6ae1f4a 100644 --- a/linearmodels/tests/system/results/execute-stata-3sls.py +++ b/linearmodels/tests/system/results/execute-stata-3sls.py @@ -45,10 +45,8 @@ STATA_PATH = os.path.join("C:\\", "Program Files (x86)", "Stata15", "StataMP-64.exe") OUTFILE = os.path.join(os.getcwd(), "stata-3sls-results.txt") -header = [ - r'use "C:\git\linearmodels\linearmodels\tests\system\results\simulated-3sls.dta",' - r" clear" -] +header = r'use "C:\git\linearmodels\linearmodels\tests\system\results\simulated-3sls.dta", clear' + all_stats = ( "estout using {outfile}, cells(b(fmt(%13.12g)) t(fmt(%13.12g)) " diff --git a/linearmodels/tests/system/results/execute-stata.py b/linearmodels/tests/system/results/execute-stata.py index f4897ccd98..ff9fb2a2dc 100644 --- a/linearmodels/tests/system/results/execute-stata.py +++ b/linearmodels/tests/system/results/execute-stata.py @@ -16,10 +16,7 @@ STATA_PATH = os.path.join("C:\\", "Program Files (x86)", "Stata13", "StataMP-64.exe") OUTFILE = os.path.join(os.getcwd(), "stata-sur-results.txt") -header = [ - r'use "C:\git\linearmodels\linearmodels\tests\system\results\simulated-sur.dta"' - ", clear" -] +header = r'use "C:\git\linearmodels\linearmodels\tests\system\results\simulated-sur.dta", clear' all_stats = ( "estout using {outfile}, cells(b(fmt(%13.12g)) " diff --git a/linearmodels/typing/data.py b/linearmodels/typing/data.py index 413688884c..6ea4971be4 100644 --- a/linearmodels/typing/data.py +++ b/linearmodels/typing/data.py @@ -4,12 +4,14 @@ import pandas as pd base_data_types = [np.ndarray, pd.DataFrame, pd.Series] + try: import xarray as xr ArrayLike = Union[np.ndarray, xr.DataArray, pd.DataFrame, pd.Series] except ImportError: + # Always needed to allow optional xarray ArrayLike = Union[np.ndarray, pd.DataFrame, pd.Series] @@ -20,6 +22,7 @@ Int64Array = np.ndarray[tuple[int, ...], np.dtype[np.int64]] # pragma: no cover Int32Array = np.ndarray[tuple[int, ...], np.dtype[np.int32]] # pragma: no cover IntArray = np.ndarray[tuple[int, ...], np.dtype[np.int_]] # pragma: no cover +AnyIntArray = np.ndarray[tuple[int, ...], np.dtype[np.integer]] # pragma: no cover BoolArray = np.ndarray[tuple[int, ...], np.dtype[np.bool_]] # pragma: no cover AnyArray = np.ndarray[tuple[int, ...], Any] # pragma: no cover Uint32Array = np.ndarray[tuple[int, ...], np.dtype[np.uint32]] # pragma: no cover @@ -27,6 +30,7 @@ FloatArray2D = np.ndarray[tuple[int, int], np.dtype[np.float64]] __all__ = [ "AnyArray", + "AnyIntArray", "ArrayLike", "BoolArray", "Float64Array", diff --git a/pyproject.toml b/pyproject.toml index 09d15d0b70..b269032f38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,13 +126,13 @@ dev = [ "pytest-xdist", "pytest-cov", # formatting - "black[jupyter]~=25.9.0", - "isort~=6.0", + "black[jupyter]~=26.5.0", + "isort~=9.0", "colorama", "flake8", "flake8-bugbear", "mypy>=1.3", - "ruff>=0.8.6", + "ruff>=0.16.0", "pyupgrade>=3.4.0", "jupyterlab-code-formatter", "jupyterlab>=4.4.8", # not directly required, pinned by Snyk to avoid a vulnerability @@ -153,7 +153,7 @@ setup = ['--vsenv'] version_file = "arch/_version.py" [tool.black] -target-version = ['py310', 'py311', 'py312', 'py313'] +target-version = ['py310', 'py311', 'py312', 'py313', 'py314'] exclude = ''' ( \.egg @@ -256,6 +256,8 @@ ignore = [ "PLR0915", # 21 # Magic number "PLR2004", + # Too many positional arguments + "PLR0917", # Like to suggest use [a, *b] instead of [a] + b "RUF005", ] @@ -376,7 +378,6 @@ omit = [ directory = "coverage_html_report" [tool.mypy] -plugins="numpy.typing.mypy_plugin" exclude = [ "tests", ] diff --git a/requirements-dev.txt b/requirements-dev.txt index 53c0e4893f..ef8620faba 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,9 +20,9 @@ matplotlib # Linting mypy>=1.3 -black[jupyter]~=25.9.0 -isort>=5.12 -ruff>=0.8.6 +black[jupyter]~=26.5.0 +isort>=9.0 +ruff>=0.16.0 flake8 flake8-bugbear pandas-stubs diff --git a/requirements-test.txt b/requirements-test.txt index efff339524..c97934cc97 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,10 +1,11 @@ -black[jupyter]~=25.9.0 +black[jupyter]~=26.5.0 coverage flake8 isort +ruff colorama matplotlib -pytest>=8.4.1,<9 +pytest>=9,<10 pytest-xdist pytest-cov pytest-randomly