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
17 changes: 5 additions & 12 deletions .github/workflows/cis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,13 @@ jobs:
tox

tests:
name: Unit tests
name: "Unit tests (Python ${{ matrix.python-version }}, ${{ matrix.pure_python && 'pure' || 'cython' }})"
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- python-version: "3.11"
toxenv: py311
- python-version: "3.12"
toxenv: py312
- python-version: "3.13"
toxenv: py313
- python-version: "3.14"
toxenv: py314
python-version: ["3.11", "3.12", "3.13", "3.14"]
pure_python: ["", "1"]
steps:
- uses: actions/checkout@v6
- name: Get history and tags for SCM versioning to work
Expand All @@ -66,9 +59,9 @@ jobs:
python -m pip install tox
- name: Test
env:
TOXENV: ${{ matrix.toxenv }}
BYTECODE_PURE_PYTHON: ${{ matrix.pure_python }}
run: |
tox
tox -e py$(echo '${{ matrix.python-version }}' | tr -d .)
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
if: github.event_name != 'schedule'
Expand Down
41 changes: 35 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ jobs:
name: cibw-sdist
path: dist/*

build_wheel:
name: Build wheel
build_pure_wheel:
name: Build pure-Python wheel
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand All @@ -52,7 +52,9 @@ jobs:
uses: actions/setup-python@v6
with:
python-version: '3.x'
- name: Build wheels
- name: Build pure-Python wheel
env:
BYTECODE_PURE_PYTHON: '1'
run: |
pip install --upgrade pip
pip install wheel build
Expand All @@ -65,12 +67,39 @@ jobs:
- name: Store artifacts
uses: actions/upload-artifact@v7
with:
name: cibw-wheel
name: cibw-wheel-pure
path: dist/*.whl

build_wheels:
name: Build wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, ubuntu-24.04-arm, macos-13, macos-latest, windows-latest]

steps:
- name: Checkout
uses: actions/checkout@v6
- name: Get history and tags for SCM versioning to work
run: |
git fetch --prune --unshallow
git fetch --depth=1 origin +refs/tags/*:refs/tags/*
- name: Build wheels
uses: pypa/cibuildwheel@v2.23.3
env:
CIBW_BUILD: cp311-* cp312-* cp313-* cp314-*
CIBW_TEST_REQUIRES: pytest
CIBW_TEST_COMMAND: python -X dev -m pytest {project}/tests
- name: Store artifacts
uses: actions/upload-artifact@v6
with:
name: cibw-wheels-${{ matrix.os }}
path: wheelhouse/*.whl

publish:
if: github.event_name == 'push'
needs: [build_wheel, build_sdist]
needs: [build_wheels, build_pure_wheel, build_sdist]
runs-on: ubuntu-latest
environment:
name: pypi
Expand Down Expand Up @@ -126,4 +155,4 @@ jobs:
run: >-
gh release upload
'${{ github.ref_name }}' dist/**
--repo '${{ github.repository }}'
--repo '${{ github.repository }}'
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ coverage.xml
.pytest_cache
.cache
.venv
*.c
*.so
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,20 @@


[build-system]
requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3"]
requires = ["setuptools>=61.2", "wheel", "setuptools_scm[toml]>=3.4.3", "cython"]
build-backend = "setuptools.build_meta"

[dependency-groups]
dev = [
"mypy>=1.16.1",
"pytest>=8",
"pytest-benchmark>=5",
"pytest-cov>=6",
"ruff>=0.12.0",
]
test = [
"pytest>=8",
"pytest-benchmark>=5",
"pytest-cov",
]

Expand Down Expand Up @@ -90,5 +92,11 @@ __version__ = "{version}"
follow_imports = "normal"
strict_optional = true

[[tool.mypy.overrides]]
# cython stubs are not available to mypy; the cythonize branch intentionally
# removes Generic[A] from BaseInstr which cascades type errors.
module = ["bytecode.instr", "bytecode.concrete", "bytecode.bytecode", "bytecode.cfg"]
ignore_errors = true

[tool.pytest.ini_options]
minversion = "6.0"
50 changes: 50 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import os

from setuptools import setup # isort: skip

from pathlib import Path

import Cython.Distutils
from Cython.Build import cythonize # noqa: I100

ROOT = Path(__file__).parent / "src"


# Get all the py files under the src folder
def get_py_files(path):
return [
p.relative_to(ROOT) for p in Path(path).rglob("*.py") if p.name != "__init__.py"
]


def pretend_cython():
return [
Cython.Distutils.Extension(
str(p.with_suffix("")).replace(os.sep, "."),
sources=[str(Path("src") / p)],
language="c",
)
for p in get_py_files(ROOT)
]


_pure_python = os.getenv("BYTECODE_PURE_PYTHON")
print(f"bytecode: building {'pure-Python' if _pure_python else 'Cython'} version")

# Include .pxd declaration files only in Cython builds so they are available
# to downstream Cython users who want to cimport from bytecode.
_package_data = {} if _pure_python else {"bytecode": ["*.pxd"]}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not dealt with that in a long time but since you specify files manually we may need to add .pyi now.


setup(
name="bytecode",
setup_requires=["setuptools_scm[toml]>=4", "cython"] + ([] if _pure_python else ["cmake>=3.24.2,<3.28"]),
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is cmake required ?

package_data=_package_data,
ext_modules=[] if _pure_python else cythonize(
pretend_cython(),
force=True,
compiler_directives={
"language_level": "3",
"annotation_typing": False,
},
),
)
5 changes: 5 additions & 0 deletions src/bytecode/concrete.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from bytecode.instr cimport BaseInstr

cdef class ConcreteInstr(BaseInstr):
cdef public object _extended_args
cdef public int _size
20 changes: 15 additions & 5 deletions src/bytecode/concrete.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
from __future__ import annotations

try:
import cython
except ImportError:

class cython: # type: ignore[no-redef]
compiled = False

@staticmethod
def cclass(cls: Any) -> Any:
return cls


import dis
import inspect
import itertools
Expand Down Expand Up @@ -85,7 +97,8 @@ def _set_docstring(code: _bytecode.BaseBytecode, consts: Sequence) -> None:
T = TypeVar("T", bound="ConcreteInstr")


class ConcreteInstr(BaseInstr[int]):
@cython.cclass
class ConcreteInstr(BaseInstr):
"""Concrete instruction.

arg must be an integer in the range 0..2147483647.
Expand All @@ -94,9 +107,6 @@ class ConcreteInstr(BaseInstr[int]):

"""

# For ConcreteInstr the argument is always an integer
_arg: int

__slots__ = ("_extended_args", "_size")

def __init__(
Expand Down Expand Up @@ -190,7 +200,7 @@ def _from_opcode(
location: Optional[InstrLocation],
) -> T:
"""Fast path for from_code: arg is a raw byte (0-255), size is always 2."""
new = object.__new__(cls)
new = cls.__new__(cls)
new._name = name
new._opcode = opcode
new._arg = arg
Expand Down
5 changes: 3 additions & 2 deletions src/bytecode/flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,17 @@ def infer_flags(
elif opcode in ASYNC_OPCODES:
known_async = True
elif opcode == YIELD_VALUE_OPCODE:
ni = next(instr_iter)
while isinstance(
ni := next(instr_iter),
ni,
(
_bytecode.SetLineno,
_bytecode.Label,
_bytecode.TryBegin,
_bytecode.TryEnd,
),
):
pass
ni = next(instr_iter)
assert ni._opcode == RESUME_OPCODE
if (ni.arg & 3) != 3:
known_generator = True
Expand Down
15 changes: 15 additions & 0 deletions src/bytecode/instr.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
cdef class InstrLocation:
# Must be `object` (not `int`) because these fields are Optional[int] and can be None.
cdef readonly object lineno
cdef readonly object end_lineno
cdef readonly object col_offset
cdef readonly object end_col_offset

cdef class BaseInstr:
cdef public str _name
cdef public object _location
cdef public int _opcode
cdef public object _arg

cdef class Instr(BaseInstr):
pass
Loading
Loading