diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000000..02247e5ccb --- /dev/null +++ b/.coveragerc @@ -0,0 +1,62 @@ +[run] +source = linearmodels +branch = True +omit = + */results/* + */_version.py + */compat/* + */conftest.py +plugins = Cython.Coverage + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain if tests don't hit defensive assertion code: + raise NotImplementedError + except NotImplementedError + raise AssertionError + + # Ignore pass + pass + + # Do not complain about missing debug-only code: + def __repr__ + if self\\.debug + + # Ignore failure messages + pytest.xfail + + # Ignore ImportError protection + raise ImportError + except ImportError + + # Ignore type checking code + if TYPE_CHECKING + elif TYPE_CHECKING + + # Cython function declarations + cdef + + # Cython functions with void + cdef void + + # Numba jit decorators + @jit + + # Do not complain if non-runnable code is not run: + if 0: + if __name__ == .__main__.: + +include = */linearmodels/* +omit = + */_version.py + */compat/* + *recursions.py + *samplers.py +ignore_errors = True + +[html] +directory = coverage_html_report diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bf04a7dc6f..fe22c95072 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -13,6 +13,7 @@ variables: PYTHONHASHSEED: 12345678 # Ensure tests are correctly gathered by xdist TEST_INSTALL: false MPLBACKEND: agg + PYTEST_PATTERN: "(not slow)" coverage: true test.install: false pip.pre: false diff --git a/ci/azure_template_posix.yml b/ci/azure_template_posix.yml index 0e5bf7da24..8c238137d5 100644 --- a/ci/azure_template_posix.yml +++ b/ci/azure_template_posix.yml @@ -16,7 +16,7 @@ jobs: vmImage: ${{ parameters.vmImage }} strategy: matrix: - python310_minimums_wheel: + python310_minimums: python.version: '3.10' NUMPY: 1.22.3 SCIPY: 1.8.0 @@ -24,7 +24,7 @@ jobs: STATSMODELS: 0.13.1 XARRAY: 0.21.0 FORMULAIC: 1.2.1 - test.wheel: true + PYTEST_PATTERN: "(slow or not slow)" python310_mid_sdist: python.version: '3.10' NUMPY: 1.23.0 @@ -62,11 +62,15 @@ jobs: XXHASH: true PYARROW: true BUILD_FLAGS: '-Csetup-args=-Dno-binary=true' + python312_cython_coverage: + python.version: '3.12' + BUILD_FLAGS: '-Csetup-args=-Dcython-coverage=true' + cython.coverage: true python313_latest: python.version: '3.13' XXHASH: true PYARROW: true - BUILD_FLAGS: '-Csetup-args=-Dcython-coverage=true' + PYTEST_PATTERN: "(slow or not slow)" python313_copy_on_write: python.version: '3.12' XXHASH: true @@ -92,8 +96,7 @@ jobs: jupyter kernelspec list displayName: 'Install dependencies' - - script: | - python -m pip list + - script: python -m pip list displayName: 'List Configuration' - script: | @@ -126,6 +129,13 @@ jobs: displayName: 'Install linearmodels (editable)' condition: and(ne(variables['test.wheel'], 'true'), ne(variables['test.sdist'], 'true')) + - script: | + pushd linearmodels/panel + cython --verbose -M --fast-fail -3 -X cpow=True -X boundscheck=False -X wraparound=False -X cdivision=True -X binding=True -X linetrace=True _utility.pyx + popd + displayName: 'Transpile Cython to C for coverage support' + condition: eq(variables['cython.coverage'], 'true') + - script: | echo "Testing site packages" mkdir test_run_dir @@ -137,16 +147,16 @@ jobs: - script: | echo "Testing editable install" - export COVERAGE_OPTS="--cov=linearmodels --cov-report xml:coverage.xml --cov-report term" - echo pytest -m "${PYTEST_PATTERN}" --junitxml=junit/test-results.xml -n auto --durations=25 ${COVERAGE_OPTS} ${BUILD_FLAGS} linearmodels/tests + export COVERAGE_OPTS="--cov-config=.coveragerc --cov=linearmodels --cov-branch --cov-report xml:coverage.xml --cov-report term" + echo pytest -m "${PYTEST_PATTERN}" --junitxml=junit/test-results.xml -n auto --durations=25 ${COVERAGE_OPTS} linearmodels/tests pytest -m "${PYTEST_PATTERN}" --junitxml=junit/test-results.xml -n auto --durations=25 ${COVERAGE_OPTS} linearmodels/tests displayName: 'Run tests (editable)' condition: and(and(ne(variables['test.wheel'], 'true'), ne(variables['test.sdist'], 'true')), ne(variables['pip.pre'], 'true')) - script: | echo "Testing pip-pre" - export COVERAGE_OPTS="--cov=linearmodels --cov-report xml:coverage.xml --cov-report term" - echo pytest -m "${PYTEST_PATTERN}" --junitxml=junit/test-results.xml -n auto --durations=25 ${COVERAGE_OPTS} ${BUILD_FLAGS} linearmodels/tests + export COVERAGE_OPTS="--cov-config=.coveragerc --cov=linearmodels --cov-branch --cov-report=xml:coverage.xml --cov-report=term" + echo pytest -m "${PYTEST_PATTERN}" --junitxml=junit/test-results.xml -n auto --durations=25 ${COVERAGE_OPTS} linearmodels/tests pytest -m "${PYTEST_PATTERN}" --junitxml=junit/test-results.xml -n auto --durations=25 ${COVERAGE_OPTS} linearmodels/tests displayName: 'Run tests (pip pre)' condition: eq(variables['pip.pre'], 'true') diff --git a/linearmodels/meson.build b/linearmodels/meson.build index b520c16e9e..3e2e917653 100644 --- a/linearmodels/meson.build +++ b/linearmodels/meson.build @@ -29,17 +29,18 @@ inc_np = include_directories(incdir_numpy, is_system: true) # Copy the main __init__.py to the build dir. # Some submodules (linalg, special, optimize) add pxd files to this. # Needed to trick Cython, it won't do a relative import outside a package +_cython_tree = [fs.copyfile('__init__.py')] cython_args = [ - '-Xcpow=True', - '-Xboundscheck=False', - '-Xwraparound=False', - '-Xcdivision=True', - '-Xbinding=True' + '-X cpow=True', + '-X boundscheck=False', + '-X wraparound=False', + '-X cdivision=True', + '-X binding=True' ] cython_c_args = ['-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION'] if get_option('cython-coverage') message('Building with Cython coverage support') - cython_args += ['-Xlinetrace=True'] + cython_args += ['-X linetrace=True'] cython_c_args += ['-DCYTHON_TRACE=1'] endif diff --git a/linearmodels/panel/meson.build b/linearmodels/panel/meson.build index d8ee29e76c..dff6acfe8b 100644 --- a/linearmodels/panel/meson.build +++ b/linearmodels/panel/meson.build @@ -5,7 +5,7 @@ if not get_option('no-binary') install: true, include_directories: [inc_np], subdir: 'linearmodels/panel', - cython_args: cython_args, c_args: cython_c_args, + cython_args: cython_args, ) endif diff --git a/pyproject.toml b/pyproject.toml index c0e2fb361c..e26ed810c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -311,9 +311,9 @@ include_trailing_comma=true use_parentheses=true [tool.coverage.run] -source = ["linearmodels"] +source = [ "linearmodels" ] branch = true -plugins = ["Cython.Coverage"] +plugins = [ "Cython.Coverage" ] omit = [ # Version file "*/_version.py", diff --git a/requirements-test.txt b/requirements-test.txt index 1f9db666e2..1d4b4d2357 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -1,4 +1,4 @@ -black[jupyter]==24.4.0 +black[jupyter]~=25.9.0 coverage flake8 isort diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/setup.py b/setup.py deleted file mode 100644 index 62725ca41f..0000000000 --- a/setup.py +++ /dev/null @@ -1,108 +0,0 @@ -from setuptools import Extension, find_namespace_packages, setup -from setuptools.dist import Distribution -from setuptools.errors import CCompilerError, ExecError, PlatformError - -import glob -import os - -try: - from Cython.Build import cythonize - - CYTHON_INSTALLED = True -except ImportError: - CYTHON_INSTALLED = False - - -FAILED_COMPILER_ERROR = """ -****************************************************************************** -* WARNING * -****************************************************************************** - -Unable to build binary modules for linearmodels. These are not required -to run any code in the package, and only provide speed-ups for a small subset -of models. - -****************************************************************************** -* WARNING * -****************************************************************************** -""" -with open("README.md") as readme: - long_description = readme.read() - - -additional_files = ["py.typed"] -for filename in glob.iglob("./linearmodels/datasets/**", recursive=True): - if ".csv.bz" in filename: - additional_files.append(filename.replace("./linearmodels/", "")) - -for filename in glob.iglob("./linearmodels/tests/**", recursive=True): - if ".txt" in filename or ".csv" in filename or ".dta" in filename: - additional_files.append(filename.replace("./linearmodels/", "")) - -for filename in glob.iglob("./examples/**", recursive=True): - if ".png" in filename: - additional_files.append(filename) - -additional_files.extend(glob.glob("./linearmodels/**/*.pyx", recursive=True)) -additional_files.extend(glob.glob("./linearmodels/**/*.pyi", recursive=True)) -additional_files.extend(glob.glob("./linearmodels/**/*.pyd", recursive=True)) -additional_files.extend(glob.glob("./linearmodels/**/*.so", recursive=True)) - - -class BinaryDistribution(Distribution): - def is_pure(self) -> bool: - return False - - -def run_setup(binary: bool = True) -> None: - extensions = [] - if binary: - import numpy - - macros = [("NPY_NO_DEPRECATED_API", "1")] - # macros.append(('CYTHON_TRACE', '1')) - directives: dict[str, bool] = {} # {'linetrace': True, 'binding':True} - extension = Extension( - "linearmodels.panel._utility", - ["linearmodels/panel/_utility.pyx"], - define_macros=macros, - include_dirs=[numpy.get_include()], - ) - extensions.append(extension) - extensions = cythonize(extensions, compiler_directives=directives, force=True) - else: - import logging - - logging.warning("Building without binary support") - - setup( - packages=["linearmodels"] - + [f"linearmodels.{v}" for v in find_namespace_packages("linearmodels")], - package_dir={"linearmodels": "./linearmodels"}, - long_description=long_description, - long_description_content_type="text/markdown", - install_requires=open("requirements.txt").read().split("\n"), - include_package_data=True, - package_data={"linearmodels": additional_files}, - zip_safe=False, - ext_modules=extensions, - distclass=BinaryDistribution, - ) - - -try: - build_binary = CYTHON_INSTALLED - build_binary &= os.environ.get("LM_NO_BINARY", None) not in ("1", "True", "true") - run_setup(binary=build_binary) -except ( - CCompilerError, - ExecError, - PlatformError, - OSError, - ValueError, - ImportError, -): - run_setup(binary=False) - import warnings - - warnings.warn(FAILED_COMPILER_ERROR, UserWarning)