Skip to content

Commit 54b0467

Browse files
committed
BLD: Add meson
1 parent 764475a commit 54b0467

File tree

12 files changed

+506
-127
lines changed

12 files changed

+506
-127
lines changed

.flake8

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[flake8]
2+
# Leave at 99 for now
3+
max-line-length = 99
4+
ignore = E203,W503,BLK100
5+
per-file-ignores =
6+
linearmodels/panel/data.py: E704
7+
linearmodels/shared/utility.py: F811, E704
8+
linearmodels/panel/utility.py: F811

ci/azure_template_posix.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ jobs:
8181
displayName: 'Use Python $(python.version)'
8282

8383
- script: |
84-
python -m pip install --upgrade pip setuptools>=61 wheel
84+
python -m pip install --upgrade pip wheel
8585
python -m pip install -r requirements.txt
8686
python -m pip install -r requirements-test.txt
8787
python -m pip install -r requirements-dev.txt

examples/system_examples.ipynb

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@
184184
"\n",
185185
"cov = res.sigma\n",
186186
"std = np.sqrt(np.diag(res.sigma)[:, None])\n",
187-
"regions = [k for k in mod_data.keys()]\n",
187+
"regions = list(mod_data.keys())\n",
188188
"corr = pd.DataFrame(cov / (std @ std.T), columns=regions, index=regions)\n",
189189
"\n",
190190
"sns.heatmap(corr, vmax=0.8, square=True)\n",
@@ -257,9 +257,7 @@
257257
"outputs": [],
258258
"source": [
259259
"# TODO: Implement method to compare across equations\n",
260-
"params = []\n",
261-
"for label in res.equation_labels:\n",
262-
" params.append(res.equations[label].params)\n",
260+
"params = [res.equations[label].params for label in res.equation_labels]\n",
263261
"params = pd.concat(params, axis=1)\n",
264262
"params.columns = res.equation_labels\n",
265263
"params.T.style.format(\"{:0.3f}\")"
@@ -732,7 +730,6 @@
732730
"outputs": [],
733731
"source": [
734732
"import statsmodels.api as sm\n",
735-
"\n",
736733
"from linearmodels.datasets import french\n",
737734
"\n",
738735
"data = french.load()\n",
@@ -779,7 +776,7 @@
779776
"name": "python",
780777
"nbconvert_exporter": "python",
781778
"pygments_lexer": "ipython3",
782-
"version": "3.12.9"
779+
"version": "3.13.7"
783780
},
784781
"pycharm": {
785782
"stem_cell": {

examples/system_three-stage-ls.ipynb

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@
135135
"metadata": {},
136136
"outputs": [],
137137
"source": [
138-
"equations = dict(hours=hours, lwage=lwage)\n",
138+
"equations = {\"hours\": hours, \"lwage\": lwage}\n",
139139
"system_2sls = IV3SLS.from_formula(equations, data)\n",
140140
"system_2sls_res = system_2sls.fit(method=\"ols\", cov_type=\"unadjusted\")\n",
141141
"print(system_2sls_res)"
@@ -154,7 +154,6 @@
154154
"metadata": {},
155155
"outputs": [],
156156
"source": [
157-
"equations = dict(hours=hours, lwage=lwage)\n",
158157
"system_3sls = IV3SLS.from_formula(equations, data)\n",
159158
"system_3sls_res = system_3sls.fit(method=\"gls\", cov_type=\"unadjusted\")\n",
160159
"print(system_3sls_res)"
@@ -196,7 +195,7 @@
196195
" \"instruments\": data[[\"age\", \"kidslt6\", \"nwifeinc\"]],\n",
197196
"}\n",
198197
"\n",
199-
"equations = dict(hours=hours, lwage=lwage)\n",
198+
"equations = {\"hours\": hours, \"lwage\": lwage}\n",
200199
"system_3sls = IV3SLS(equations)\n",
201200
"system_3sls_res = system_3sls.fit(cov_type=\"unadjusted\")\n",
202201
"print(system_3sls_res)"
@@ -223,10 +222,10 @@
223222
"metadata": {},
224223
"outputs": [],
225224
"source": [
226-
"equations = dict(\n",
227-
" hours=\"hours ~ educ + age + kidslt6 + nwifeinc + [lwage ~ exper + expersq]\",\n",
228-
" lwage=\"lwage ~ educ + exper + expersq + [hours ~ age + kidslt6 + nwifeinc]\",\n",
229-
")\n",
225+
"equations = {\n",
226+
" \"hours\": \"hours ~ educ + age + kidslt6 + nwifeinc + [lwage ~ exper + expersq]\",\n",
227+
" \"lwage\": \"lwage ~ educ + exper + expersq + [hours ~ age + kidslt6 + nwifeinc]\",\n",
228+
"}\n",
230229
"system_gmm = IVSystemGMM.from_formula(equations, data, weight_type=\"unadjusted\")\n",
231230
"system_gmm_res = system_gmm.fit(cov_type=\"unadjusted\")\n",
232231
"print(system_gmm_res)"
@@ -295,7 +294,7 @@
295294
"in_sample = df.iloc[:-10000]\n",
296295
"oos = df.iloc[-10000:]\n",
297296
"mod = IV3SLS.from_formula(\n",
298-
" dict(y1=\"y1 ~ x1 + [y2 ~ x2]\", y2=\"y2 ~ x2 + [y1 ~ x1]\"), data=df\n",
297+
" {\"y1\": \"y1 ~ x1 + [y2 ~ x2]\", \"y2\": \"y2 ~ x2 + [y1 ~ x1]\"}, data=df\n",
299298
")\n",
300299
"res = mod.fit()\n",
301300
"print(res)"
@@ -353,7 +352,7 @@
353352
"name": "python",
354353
"nbconvert_exporter": "python",
355354
"pygments_lexer": "ipython3",
356-
"version": "3.12.9"
355+
"version": "3.13.7"
357356
},
358357
"pycharm": {
359358
"stem_cell": {

linearmodels/_build/__init__.py

Whitespace-only changes.

linearmodels/_build/git_version.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!/usr/bin/env python3
2+
from __future__ import annotations
3+
4+
import os
5+
import setuptools_scm
6+
from packaging.version import Version
7+
from pathlib import Path
8+
9+
ROOT = Path(__file__).parent.parent.parent.absolute()
10+
11+
12+
def get_version() -> tuple[str, tuple[int | str, ...]]:
13+
_version = setuptools_scm.get_version(root=ROOT)
14+
parsed_version = Version(_version)
15+
version_fields: tuple[int | str, ...] = parsed_version.release
16+
if parsed_version.epoch:
17+
version_fields = (f"{parsed_version.epoch}!", *version_fields)
18+
if parsed_version.pre is not None:
19+
version_fields += (f"{parsed_version.pre[0]}{parsed_version.pre[1]}",)
20+
21+
if parsed_version.post is not None:
22+
version_fields += (f"post{parsed_version.post}",)
23+
24+
if parsed_version.dev is not None:
25+
version_fields += (f"dev{parsed_version.dev}",)
26+
27+
if parsed_version.local is not None:
28+
version_fields += (parsed_version.local,)
29+
30+
return _version, version_fields
31+
32+
33+
def write_version_file(
34+
filename: str, version: str, version_fields: tuple[int | str, ...]
35+
) -> None:
36+
template = f"""# file generated by setuptools-scm
37+
# don't change, don't track in version control
38+
39+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
40+
41+
TYPE_CHECKING = False
42+
if TYPE_CHECKING:
43+
from typing import Tuple
44+
from typing import Union
45+
46+
VERSION_TUPLE = Tuple[Union[int, str], ...]
47+
else:
48+
VERSION_TUPLE = object
49+
50+
version: str
51+
__version__: str
52+
__version_tuple__: VERSION_TUPLE
53+
version_tuple: VERSION_TUPLE
54+
55+
__version__ = version = '{version}'
56+
__version_tuple__ = version_tuple = {version_fields}
57+
"""
58+
59+
with open(filename, "w") as f:
60+
f.write(template)
61+
62+
63+
if __name__ == "__main__":
64+
import argparse
65+
66+
parser = argparse.ArgumentParser()
67+
parser.add_argument("--write", help="Save version to this file")
68+
parser.add_argument(
69+
"--meson-dist",
70+
help="Output path is relative to MESON_DIST_ROOT",
71+
action="store_true",
72+
)
73+
args = parser.parse_args()
74+
75+
version, version_tuple = get_version()
76+
77+
if args.write:
78+
outfile = args.write
79+
if args.meson_dist:
80+
outfile = os.path.join(os.environ.get("MESON_DIST_ROOT", ""), outfile)
81+
82+
# Print human readable output path
83+
relpath = os.path.relpath(outfile)
84+
if relpath.startswith("."):
85+
relpath = outfile
86+
write_version_file(relpath, version, version_tuple)
87+
else:
88+
print(version)

linearmodels/meson.build

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
incdir_numpy = run_command(
2+
py,
3+
[
4+
'-c',
5+
'''
6+
import os
7+
cwd = os.getcwd()
8+
9+
# Protect from import errors due to module names
10+
os.chdir(os.path.join('..', 'examples'))
11+
import numpy as np
12+
os.chdir(cwd)
13+
14+
try:
15+
# Check if include directory is inside the dir
16+
# e.g. a venv created inside the dir
17+
# If so, convert it to a relative path
18+
incdir = os.path.relpath(np.get_include())
19+
except Exception as exc:
20+
incdir = np.get_include()
21+
print(incdir)
22+
''',
23+
],
24+
check: true,
25+
).stdout().strip()
26+
27+
inc_np = include_directories(incdir_numpy, is_system: true)
28+
29+
# Copy the main __init__.py to the build dir.
30+
# Some submodules (linalg, special, optimize) add pxd files to this.
31+
# Needed to trick Cython, it won't do a relative import outside a package
32+
_cython_tree = [fs.copyfile('__init__.py')]
33+
cython_args = []
34+
cython_c_args = ['-DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION']
35+
if get_option('cython-coverage')
36+
cython_args += ['-Xlinetrace=True']
37+
cython_c_args += ['-DCYTHON_TRACE=1']
38+
endif
39+
40+
lm_dir = py.get_install_dir() / 'linearmodels'
41+
# Generate version.py for sdist
42+
meson.add_dist_script(
43+
['_build/git_version.py', '--meson-dist', '--write',
44+
'linearmodels/_version.py']
45+
)
46+
if not fs.exists('_version.py')
47+
generate_version = custom_target(
48+
'generate-version',
49+
install: true,
50+
build_always_stale: true,
51+
build_by_default: true,
52+
output: '_version.py',
53+
input: '_build/git_version.py',
54+
command: [py, '@INPUT@', '--write', '@OUTPUT@'],
55+
install_dir: lm_dir,
56+
install_tag: 'python-runtime'
57+
)
58+
else
59+
# When building from sdist, version.py exists and should be included
60+
py.install_sources(
61+
['_version.py'],
62+
63+
)
64+
endif
65+
66+
subdir('panel')
67+
68+
subdirs_list = [
69+
'__future__',
70+
'_build',
71+
'asset_pricing',
72+
'compat',
73+
'datasets',
74+
'iv',
75+
'panel',
76+
'shared',
77+
'system',
78+
'tests',
79+
'typing',
80+
]
81+
82+
foreach subdir : subdirs_list
83+
install_subdir(subdir, install_dir: py.get_install_dir() / 'linearmodels')
84+
endforeach
85+
86+
top_level_py_list = [
87+
'__init__.py',
88+
'conftest.py',
89+
'formula.py',
90+
'py.typed',
91+
]
92+
93+
py.install_sources(top_level_py_list, subdir: 'linearmodels')

linearmodels/panel/meson.build

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
if not get_option('no-binary')
2+
py.extension_module(
3+
'_utility',
4+
'_utility.pyx',
5+
install: true,
6+
include_directories: [inc_np],
7+
subdir: 'linearmodels/panel',
8+
cython_args: cython_args,
9+
c_args: cython_c_args,
10+
)
11+
endif

meson.build

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
project(
2+
'linearmodels',
3+
'c', 'cython',
4+
license: 'NCSA',
5+
meson_version: '>= 1.9.0',
6+
default_options: [],
7+
version: run_command(['linearmodels/_build/git_version.py'], check: true).stdout().strip(),
8+
)
9+
10+
cc = meson.get_compiler('c')
11+
cy = meson.get_compiler('cython')
12+
cython = find_program(cy.cmd_array()[0])
13+
14+
if not cy.version().version_compare('>=3.0.10')
15+
error('linearmodels requires Cython >= 3.0.10')
16+
endif
17+
18+
py = import('python').find_installation(pure: false)
19+
fs = import('fs')
20+
21+
py.install_sources('pyproject.toml', subdir: 'linearmodels')
22+
subdir('linearmodels')

meson.options

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Use this options by adding to the pip command:
2+
# -Csetup-args="-Dcython-coverage=true"
3+
# then run:
4+
# pytest --cov=statsmodels statsmodels
5+
# coverage html
6+
option('cython-coverage', type: 'boolean', value: false,
7+
description: 'Compile and build cython modules with coverage support')
8+
# Use this options by adding to the pip command:
9+
# -Csetup-args="-Dno-binary=true"
10+
option('no-binary', type: 'boolean', value: false,
11+
description: 'Do not build any Cython extensions')

0 commit comments

Comments
 (0)