Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: setup cython example #5

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 10 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
3 changes: 2 additions & 1 deletion .devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.black-formatter"
"ms-python.black-formatter",
"ms-vscode.cpptools-extension-pack"
],
"settings": {
"python.analysis.typeCheckingMode": "strict",
Expand Down
35 changes: 35 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,38 @@ fil-result/

# OSX
*.DS_Store*

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so
*.pyd

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# Cython
panct/*.c
panct/*.cpp
panct/*.html
2 changes: 2 additions & 0 deletions NOTES.md
Copy link
Member Author

Choose a reason for hiding this comment

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

TODO: delete this file before merging

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
1. How do we want to include the gbz-base source code in the repo? Options: git submodule or install as setup step
2. Do we want to automatically skip compilation of the cython module if it doesn't work? Or should we just fail/error explicitly. The former could be helpful if we want to implement some kind of slower alternative
85 changes: 85 additions & 0 deletions build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# type: ignore
import os
import shutil
from pathlib import Path

# This file was adapted from https://github.com/BrianPugh/python-template/blob/main/build.py

# Uncomment if library can still function if extensions fail to compile (e.g. slower, python fallback).
# Don't allow failure if cibuildwheel is running.
# allowed_to_fail = os.environ.get("CIBUILDWHEEL", "0") != "1"
allowed_to_fail = False


def build_cython_extensions():
# when using setuptools, you should import setuptools before Cython,
# otherwise, both might disagree about the class to use.
from setuptools import Extension # noqa: I001
from setuptools.dist import Distribution # noqa: I001
import Cython.Compiler.Options # pyright: ignore [reportMissingImports]
from Cython.Build import (
build_ext,
cythonize,
) # pyright: ignore [reportMissingImports]

Cython.Compiler.Options.annotate = True

if os.name == "nt": # Windows
extra_compile_args = [
"/O2",
]
else: # UNIX-based systems
extra_compile_args = [
"-O3",
"-Werror",
"-Wno-unreachable-code-fallthrough",
"-Wno-deprecated-declarations",
"-Wno-parentheses-equality",
]
extra_compile_args.append("-UNDEBUG") # Cython disables asserts by default.
# Relative to project root director
include_dirs = [
"panct/",
"panct/_c_src",
]

c_files = [str(x) for x in Path("panct/_c_src").rglob("*.c")]
extensions = [
Extension(
# Your .pyx file will be available to cpython at this location.
"panct._c_extension",
[
# ".c" and ".pyx" source file paths
"panct/_c_extension.pyx",
*c_files,
],
include_dirs=include_dirs,
extra_compile_args=extra_compile_args,
language="c",
),
]

include_dirs = set()
for extension in extensions:
include_dirs.update(extension.include_dirs)
include_dirs = list(include_dirs)

ext_modules = cythonize(
extensions, include_path=include_dirs, language_level=3, annotate=True
)
dist = Distribution({"ext_modules": ext_modules})
cmd = build_ext(dist)
cmd.ensure_finalized()
cmd.run()

for output in cmd.get_outputs():
output = Path(output)
relative_extension = output.relative_to(cmd.build_lib)
shutil.copyfile(output, relative_extension)


try:
build_cython_extensions()
except Exception:
if not allowed_to_fail:
raise
15 changes: 14 additions & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ def docs(session: Session) -> None:
if build_dir.exists():
shutil.rmtree(build_dir)

session.install(".")
session.install(
"sphinx",
"sphinx-autodoc-typehints",
"sphinx-rtd-theme",
"numpydoc",
"sphinx-click",
)
session.run("sphinx-build", *args)


Expand Down Expand Up @@ -63,10 +71,12 @@ def install_handle_python_numpy(session):
@session(venv_backend=conda_cmd, venv_params=conda_args, python=python_versions)
def tests(session: Session) -> None:
"""Run the test suite."""
# first, delete any existing envs to avoid
# https://github.com/cjolowicz/nox-poetry/issues/1188
session.run("poetry", "env", "remove", "--all")
session.conda_install(
"coverage[toml]",
"pytest",
"numpy>=1.20.0",
channel="conda-forge",
)
install_handle_python_numpy(session)
Expand All @@ -83,6 +93,9 @@ def tests(session: Session) -> None:
@session(python=python_versions)
def tests(session: Session) -> None:
"""Run the test suite."""
# first, delete any existing envs to avoid
# https://github.com/cjolowicz/nox-poetry/issues/1188
session.run("poetry", "env", "remove", "--all")
session.install("coverage[toml]", "pytest")
install_handle_python_numpy(session)
try:
Expand Down
6 changes: 1 addition & 5 deletions panct/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
try:
from importlib.metadata import version, PackageNotFoundError
except ImportError:
# handles py3.7, since importlib.metadata was introduced in py3.8
from importlib_metadata import version, PackageNotFoundError
from importlib.metadata import version, PackageNotFoundError

try:
__version__ = version(__name__)
Expand Down
5 changes: 5 additions & 0 deletions panct/_c_extension.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Foo:
def __init__(self): ...
def __call__(self): ...

def divide(x: float, y: float) -> float: ...
40 changes: 40 additions & 0 deletions panct/_c_extension.pyx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
cimport cpythontemplate # See cpythontemplate.pxd
# Invoke PyErr_CheckSignals() occasionally if your C code runs long.
# This allows your code to be interrupted via ctrl+c.
from cpython.exc cimport PyErr_CheckSignals
from cpython.mem cimport PyMem_Malloc, PyMem_Free
from libc.stddef cimport size_t


cdef class Foo:
"""Pythonic interface to the C "foo" struct."""
cdef cpythontemplate.foo_t * _object

def __cinit__(self):
# Automatically called before __init__.
# All arguments passed to __init__ are also passed to __cinit__.
# * As a convenience, if __cinit__() takes no arguments (other than self), it will
# ignore arguments passed to the constructor without complaining about signature mismatch.
# Allocate memory for C objects here.
self._object = <cpythontemplate.foo_t *>PyMem_Malloc(sizeof(cpythontemplate.foo_t))
if self._object is NULL:
raise MemoryError

def __dealloc__(self):
# Should "undo" __cinit__
PyMem_Free(self._object)

def __init__(self):
cpythontemplate.foo_init(self._object)

def __call__(self):
# invoke increment
cpythontemplate.foo_increment(self._object)

# Functions declared with cpdef are visible to both cython and python.
# https://cython.readthedocs.io/en/latest/src/userguide/language_basics.html#python-functions-vs-c-functions
# https://cython.readthedocs.io/en/latest/src/userguide/language_basics.html#error-return-values
cpdef float divide(float x, float y) except? 1.23:
if y == 0.0:
raise ZeroDivisionError
return x / y
11 changes: 11 additions & 0 deletions panct/_c_src/foo.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "foo.h"

int foo_init(foo_t *foo){
foo->counter = 0;
return FOO_OK;
}

int foo_increment(foo_t *foo){
foo->counter++;
return foo->counter;
}
24 changes: 24 additions & 0 deletions panct/_c_src/foo.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#ifndef FOO_H
#define FOO_H

#ifdef __cplusplus
extern "C" {
#endif

typedef enum {
FOO_OK = 0,
} foo_res_t;

typedef struct {
int counter;
} foo_t;

int foo_init(foo_t *foo);

int foo_increment(foo_t *foo);

#ifdef __cplusplus
}
#endif

#endif
26 changes: 26 additions & 0 deletions panct/cpythontemplate.pxd
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Primary cimport.

This file acts as an interface between cython and C header files.

Other pyx files will be able to "cimport cpythontemplate" to gain
access to functions and structures defined here.

This file should be a simple translation from existing c header file(s).
Multiple header files may be translated here.
"""

from libcpp cimport bool
from libc.stdint cimport uint8_t, uint32_t

cdef extern from "foo.h":
# Translate typedef'd structs:
ctypedef struct foo_t:
int counter

# Translate enums:
ctypedef enum foo_res_t:
FOO_OK = 0,

# Typical function declaration
void foo_init(foo_t * foo);
void foo_increment(foo_t * foo);
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[build-system]
requires = ["poetry-core>=1.0.0"]
requires = ["poetry-core>=1.0.0", "Cython>=0.3.11", "setuptools>=75.1.0"]
build-backend = "poetry.core.masonry.api"

[tool.poetry]
Expand All @@ -12,6 +12,7 @@ repository = "https://github.com/CAST-genomics/panCT"
homepage = "https://github.com/CAST-genomics/panCT"
documentation = "https://panCT.readthedocs.io"
readme = "README.md"
include = ["panct/*.so", "panct/*.pyd"] # Compiled extensions

[tool.poetry.dependencies]
python = ">=3.9"
Expand All @@ -34,6 +35,10 @@ ipython = ">=7.34.0"
coverage = {extras = ["toml"], version = ">=7.2.7"}
filprofiler = ">=2023.3.1"

[tool.poetry.build]
generate-setup-file = false
script = "build.py"

[tool.poetry.scripts]
panct = 'panct.__main__:app'

Expand Down
Loading