Skip to content

Commit

Permalink
Initial check-in
Browse files Browse the repository at this point in the history
  • Loading branch information
funkey committed Mar 12, 2024
0 parents commit f4def86
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 0 deletions.
20 changes: 20 additions & 0 deletions .cruft.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"template": "https://github.com/adjavon/pycookie/",
"commit": "d7bd6b8521627b130001b270383b72bfed608ab3",
"checkout": null,
"context": {
"cookiecutter": {
"full_name": "Jan Funke",
"email": "[email protected]",
"github_username": "funkelab",
"project_name": "witty",
"project_slug": "witty",
"project_short_description": "Well-in-Time Compiler for Cython Modules",
"_copy_without_render": [
".github/workflows/*"
],
"_template": "https://github.com/adjavon/pycookie/"
}
},
"directory": null
}
17 changes: 17 additions & 0 deletions .github/workflows/black.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Python Black

on: [push, pull_request]

jobs:
lint:
name: Python Lint
runs-on: ubuntu-latest
steps:
- name: Setup Python
uses: actions/setup-python@v4
- name: Setup checkout
uses: actions/checkout@master
- name: Lint with Black
run: |
pip install black
black --diff --check src/witty tests
18 changes: 18 additions & 0 deletions .github/workflows/mypy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Python mypy

on: [push, pull_request]

jobs:
static-analysis:
name: Python mypy
runs-on: ubuntu-latest
steps:
- name: Setup Python
uses: actions/setup-python@v4
- name: Setup checkout
uses: actions/checkout@v2
- name: mypy
run: |
pip install .
pip install --upgrade mypy
mypy src/witty
25 changes: 25 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Test

on:
push:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10"]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
pip install ".[dev]"
- name: Test with pytest
run: |
pytest tests
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
*.sw[pmno]
*.pyc
*.egg-info
*.dat
*.lock
*.so
*_wrapper.cpp
*_wrapper.c
.coverage
build
dist
.vscode
docs/_build/
25 changes: 25 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ci:
autoupdate_schedule: monthly
autofix_commit_msg: "style(pre-commit.ci): auto fixes [...]"
autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate"

default_install_hook_types: [pre-commit, commit-msg]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files

- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.0.1
hooks:
- id: mypy
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# witty

[![tests](https://github.com/funkelab/witty/actions/workflows/tests.yaml/badge.svg)](https://github.com/funkelab/witty/actions/workflows/tests.yaml)
32 changes: 32 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = ["setuptools", "wheel"]

[project]
name = "witty"
description = "Well-in-Time Compiler for Cython Modules"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python :: 3",
]
keywords = []
license = { text = "BSD 3-Clause License" }
authors = [
{ email = "[email protected]", name = "Jan Funke" },
]
dynamic = ["version"]
dependencies = ["cython"]

[project.optional-dependencies]
dev = [
'pytest',
'black',
'mypy',
'pdoc',
'pre-commit'
]

[project.urls]
homepage = "https://github.com/funkelab/witty"
repository = "https://github.com/funkelab/witty"
1 change: 1 addition & 0 deletions src/witty/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .compile_module import compile_module
99 changes: 99 additions & 0 deletions src/witty/compile_module.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Cython
import fcntl
import hashlib
import importlib.util
import sys
from Cython.Build import cythonize
from Cython.Build.Inline import to_unicode, _get_build_extension
from Cython.Utils import get_cython_cache_dir
from distutils.core import Extension
from pathlib import Path


def load_dynamic(module_name, module_lib):
spec = importlib.util.spec_from_file_location(module_name, module_lib)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
return sys.modules[module_name]


def compile_module(
source_pyx,
source_files=None,
include_dirs=None,
library_dirs=None,
language="c",
extra_compile_args=None,
extra_link_args=None,
name=None,
force_rebuild=False,
):
if source_files is None:
source_files = []
if include_dirs is None:
include_dirs = ["."]
if library_dirs is None:
library_dirs = []
if name is None:
name = "_wit_module"

source_pyx = to_unicode(source_pyx)
sources = [source_pyx]

for source_file in source_files:
sources.append(open(source_file, "r").read())

source_hashes = [
hashlib.md5(source.encode("utf-8")).hexdigest() for source in sources
]
source_key = (source_hashes, sys.version_info, sys.executable, Cython.__version__)
module_hash = hashlib.md5(str(source_key).encode("utf-8")).hexdigest()
module_name = name + "_" + module_hash

# already loaded?
if module_name in sys.modules and not force_rebuild:
return sys.modules[module_name]

build_extension = _get_build_extension()
module_ext = build_extension.get_ext_filename("")
module_dir = Path(get_cython_cache_dir()) / "witty"
module_pyx = (module_dir / module_name).with_suffix(".pyx")
module_lib = (module_dir / module_name).with_suffix(module_ext)
module_lock = (module_dir / module_name).with_suffix(".lock")

print(f"Compiling {module_name} into {module_lib}...")

module_dir.mkdir(parents=True, exist_ok=True)

# make sure the same module is not build concurrently
with open(module_lock, "w") as lock_file:
fcntl.lockf(lock_file, fcntl.LOCK_EX)

# already compiled?
if module_lib.is_file() and not force_rebuild:
print(f"Reusing already compiled module from {module_lib}")
return load_dynamic(module_name, module_lib)

# create pyx file
with open(module_pyx, "w") as f:
f.write(source_pyx)

extension = Extension(
module_name,
sources=[str(module_pyx)],
include_dirs=include_dirs,
library_dirs=library_dirs,
language=language,
extra_compile_args=extra_compile_args,
extra_link_args=extra_link_args,
)

build_extension.extensions = cythonize(
[extension], compiler_directives={"language_level": "3"}
)
build_extension.build_temp = str(module_dir)
build_extension.build_lib = str(module_dir)
build_extension.run()

return load_dynamic(module_name, module_lib)
36 changes: 36 additions & 0 deletions tests/test_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import witty


def test_compile():
source_pxy_template = """
cdef extern from '<vector>' namespace 'std':
cdef cppclass vector[T]:
vector()
void push_back(T& item)
size_t size()
def add({type} x, {type} y):
return x + y
def to_vector({type} x):
v = vector[{type}]()
v.push_back(x)
return v
"""

module_int = witty.compile_module(
source_pxy_template.format(type="int"), language="c++", force_rebuild=True
)
result = module_int.add(3, 4)

assert result == 7
assert type(result) == int

module_float = witty.compile_module(
source_pxy_template.format(type="float"), language="c++"
)
result = module_float.add(3, 4)

assert result == 7
assert type(result) == float

0 comments on commit f4def86

Please sign in to comment.