Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ jobs:
shell: bash -l {0}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
with:
version: "0.9.6"
version: "0.13.4"
enable-cache: true
cache-dependency-glob: "uv.lock"
python-version: '3.11'
Expand All @@ -66,16 +66,16 @@ jobs:
strategy:
matrix:
os: ["ubuntu-latest", "windows-latest"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13", "3.14"]
fail-fast: false
defaults:
run:
shell: bash -l {0}
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v7
- uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
with:
version: "0.9.6"
version: "0.13.4"
python-version: ${{ matrix.python-version }}
- name: File contents
run: |
Expand Down Expand Up @@ -110,11 +110,11 @@ jobs:
shell: bash -l {0}
steps:
- uses: actions/checkout@v4
- name: Install uv and set Python 3.10
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
- name: Install uv and set Python 3.11
uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
with:
version: "0.12.2"
python-version: "3.10"
version: "0.13.4"
python-version: "3.11"
enable-cache: true
- name: Install dependencies
run: |
Expand Down Expand Up @@ -152,7 +152,7 @@ jobs:
# test that updates to sharrow will not break the activitysim canonical examples
needs: fmt
env:
python-version: "3.10"
python-version: "3.11"
label: linux-64
# limit glibc malloc arenas, which can otherwise retain large amounts
# of memory freed during numba compilation
Expand Down Expand Up @@ -191,9 +191,9 @@ jobs:
path: 'activitysim'

- name: Setup UV
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
with:
version: "0.9.6"
version: "0.13.4"
enable-cache: true
cache-dependency-glob: "uv.lock"
python-version: ${{ env.python-version }}
Expand Down Expand Up @@ -230,16 +230,20 @@ jobs:
run: |
uv venv
source .venv/bin/activate
# pandera<0.35: ActivitySim's school_escorting uses np.NaN, which
# only resolves under numpy 2 because pandera <0.35 patched it back
# into numpy on import. Drop the cap once ActivitySim uses np.nan.
# zarr matches sharrow's own requirement (zarr 3 API).
uv pip install "black==22.12.0" "coveralls>=4" \
"cytoolz==0.12.2" "dask>=2026" "isort>=5.12.0" \
"multimethod<2.0" "nbmake>=1.4.6" "numba>=0.57" \
"numpy>=2,<3" "openmatrix==0.3.5.0" \
"pandera>=0.30" "pandas>=2,<3" "platformdirs>=3.2" \
"pandera>=0.30,<0.35" "pandas>=2,<3" "platformdirs>=3.2" \
"psutil>=5.9" "pyarrow>=11.0" "pydantic>=2.6,<3" "pypyr>=5.8" \
"tables>=3.9" "pytest>=7.2" "pytest-cov" "pytest-regressions" \
"pyyaml>=6" "requests>=2.28" "ruff" "scikit-learn>=1.2" \
"simwrapper>1.7" "sparse" "xarray>=2025" \
"zarr>=2,<3" "zstandard" \
"zarr>=3.1.3,<4" "zstandard" \
./sharrow ./activitysim

- name: UV checkup
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@ numba for ActivitySim-style spec files

## Building a Wheel

To build a wheel for sharrow, you need to have `build` installed. You can
install it with `python -m pip install build`.

Then run the builder:
Build a wheel for sharrow with [uv](https://docs.astral.sh/uv/) 0.13.4 or newer:

```shell
python -m build .
uv build
```


Expand All @@ -29,10 +26,15 @@ to ensure that they pass the quality checks. To install and use this hook,
run the following commands:

```shell
python -m pip install pre-commit # if needed
uv tool install pre-commit # if needed
pre-commit install
```

If you installed pre-commit before 21 October 2026, upgrade it (for example with
`uv tool upgrade pre-commit`) so that it uses virtualenv 21.14.12 or newer.
Older virtualenv versions set up hook environments with a pip that trusts the
compromised eMudhra certificate authority (see the [incident notice](https://discuss.python.org/t/information-for-python-users-regarding-emudhra-emsign-ca-certificate-security-incident/109576)).

Then, when you try to make a commit, your code will be checked locally to ensure
that your code passes the quality checks. If you want to run the checks manually,
you can do so with the following command:
Expand Down
6 changes: 4 additions & 2 deletions envs/development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ channels:
- conda-forge
- nodefaults
dependencies:
- python=3.9
- pip
- python=3.11
# eMudhra CA incident: pip and certifi versions that drop the compromised CA
- pip>=26.2.2
- certifi>=2026.10.19
# required for testing
- dask
- filelock
Expand Down
4 changes: 3 additions & 1 deletion envs/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ channels:
- conda-forge
- nodefaults
dependencies:
- pip
# eMudhra CA incident: pip and certifi versions that drop the compromised CA
- pip>=26.2.2
- certifi>=2026.10.19
- numpy>=1.19,<2
- pandas>=1.2
- pyarrow
Expand Down
18 changes: 12 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "sharrow"
requires-python = ">=3.9"
requires-python = ">=3.11"
dynamic = ["version"]
dependencies = [
"numpy >= 1.19",
Expand All @@ -16,7 +16,7 @@ dependencies = [
"h5py",
"blosc2",
"hdf5plugin",
"xarray",
"xarray >= 2025.3.1",
"numba >= 0.57",
"numexpr",
"filelock",
Expand All @@ -26,9 +26,10 @@ dependencies = [
classifiers = [
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
description = "numba for ActivitySim-style spec files"
readme = "README.md"
Expand All @@ -52,7 +53,7 @@ write_to = "sharrow/_version.py"
[tool.ruff]
fix = true
line-length = 88
target-version = "py39"
target-version = "py311"
extend-include = ["*.ipynb"]

[tool.ruff.lint]
Expand Down Expand Up @@ -97,5 +98,10 @@ dev = [
"pytest-regressions>=2.8.3",
"scipy>=1.13.1",
"sparse>=0.15.5",
"zarr>=2.18.2",
"zarr>=3.1.3,<4",
]

[tool.uv]
# eMudhra incident, see https://discuss.python.org/t/information-for-python-users-regarding-emudhra-emsign-ca-certificate-security-incident/109576
required-version = ">=0.13.4"
constraint-dependencies = ["certifi>=2026.10.19"]
36 changes: 17 additions & 19 deletions sharrow/aster.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def unparse_(*args):


def ast_String_value(x):
return x.value if isinstance(x, ast.Str) else x
return x.value if isinstance(x, ast.Constant) and isinstance(x.value, str) else x


ast_TupleIndex_Type = ast.Tuple
Expand Down Expand Up @@ -469,9 +469,7 @@ def _maybe_transpose_first_two_args(_slice):
if isinstance(n, int):
elts.append(ast.Name(id=f"_arg{n:02}", ctx=ast.Load()))
elif isinstance(n, dict):
elts.append(
ast.Constant(n=n[missing_dim_value], ctx=ast.Load())
)
elts.append(ast.Constant(value=n[missing_dim_value]))
else:
elts.append(n)
logger.debug(f"ELT {unparse_(elts[-1])}")
Expand Down Expand Up @@ -534,12 +532,12 @@ def _maybe_transpose_first_two_args(_slice):
scale = digital_encoding.get("scale", 1)
offset = digital_encoding.get("offset", 0)
result = ast.Call(
func=ast.Name("digital_decode", cts=ast.Load()),
func=ast.Name("digital_decode", ctx=ast.Load()),
args=[
result,
ast.Num(scale),
ast.Num(offset),
ast.Num(missing_value),
ast.Constant(value=scale),
ast.Constant(value=offset),
ast.Constant(value=missing_value),
],
keywords=[],
)
Expand All @@ -550,13 +548,13 @@ def _maybe_transpose_first_two_args(_slice):
result = ast.BinOp(
left=result,
op=ast.Mult(),
right=ast.Num(scale),
right=ast.Constant(value=scale),
)
if offset:
result = ast.BinOp(
left=result,
op=ast.Add(),
right=ast.Num(offset),
right=ast.Constant(value=offset),
)

blender = self.blenders.get(attr, None)
Expand All @@ -582,7 +580,7 @@ def _maybe_transpose_first_two_args(_slice):
ast_Constant(max_blend_distance), # blend_limit
)
result = ast.Call(
func=ast.Name("get_blended_2", cts=ast.Load()),
func=ast.Name("get_blended_2", ctx=ast.Load()),
args=get_blended_2_args,
keywords=[],
)
Expand Down Expand Up @@ -781,14 +779,14 @@ def visit_Call(self, node):
for a in node.args:
clip_args.append(self.visit(a))
result = ast.Call(
func=ast.Name("clip", cts=ast.Load()),
func=ast.Name("clip", ctx=ast.Load()),
args=clip_args,
keywords=[self.visit(i) for i in node.keywords],
)
elif len(node.args) == 1 and len(node.keywords) == 0:
# single positional arg becomes max
result = ast.Call(
func=ast.Name("max", cts=ast.Load()),
func=ast.Name("max", ctx=ast.Load()),
args=[self.visit(node.func.value), self.visit(node.args[0])],
keywords=[],
)
Expand All @@ -797,7 +795,7 @@ def visit_Call(self, node):
if node.keywords[0].arg in ("upper", "max"):
# becomes min
result = ast.Call(
func=ast.Name("min", cts=ast.Load()),
func=ast.Name("min", ctx=ast.Load()),
args=[
self.visit(node.func.value),
self.visit(node.keywords[0].value),
Expand All @@ -807,7 +805,7 @@ def visit_Call(self, node):
elif node.keywords[0].arg in ("lower", "min"):
# becomes max
result = ast.Call(
func=ast.Name("max", cts=ast.Load()),
func=ast.Name("max", ctx=ast.Load()),
args=[
self.visit(node.func.value),
self.visit(node.keywords[0].value),
Expand All @@ -817,12 +815,12 @@ def visit_Call(self, node):
elif len(node.args) == 2 and len(node.keywords) == 0:
# two positional arg becomes max and min
nested_call = ast.Call(
func=ast.Name("min", cts=ast.Load()),
func=ast.Name("min", ctx=ast.Load()),
args=[self.visit(node.func.value), self.visit(node.args[1])],
keywords=[],
)
result = ast.Call(
func=ast.Name("max", cts=ast.Load()),
func=ast.Name("max", ctx=ast.Load()),
args=[nested_call, self.visit(node.args[0])],
keywords=[],
)
Expand All @@ -834,7 +832,7 @@ def visit_Call(self, node):
for a in node.args:
clip_args.append(self.visit(a))
result = ast.Call(
func=ast.Name("clip", cts=ast.Load()),
func=ast.Name("clip", ctx=ast.Load()),
args=clip_args,
keywords=[self.visit(i) for i in node.keywords],
)
Expand Down Expand Up @@ -938,7 +936,7 @@ def visit_Call(self, node):
cat_is_lt_zero = ast.Compare(
left=apply_args[0].slice,
ops=[ast.Lt()],
comparators=[ast.Num(0)],
comparators=[ast.Constant(value=0)],
)
result = ast.BoolOp(
op=ast.Or(), values=[cat_is_lt_zero, result]
Expand Down
Loading
Loading