Skip to content

Commit c6e197b

Browse files
committed
typex: first code release.
1 parent f7d0d1e commit c6e197b

11 files changed

Lines changed: 535 additions & 20 deletions

File tree

.github/dependabot.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
version: 2
2+
updates:
3+
- package-ecosystem: "github-actions"
4+
directory: "/"
5+
schedule:
6+
interval: "monthly"

.github/workflows/linters.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: "RUFF"
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
pull_request:
8+
branches:
9+
- "*"
10+
11+
jobs:
12+
ruff:
13+
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest]
19+
python-version: [3.12]
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0
26+
- name: Set up Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: ${{ matrix.python-version }}
30+
- name: Install dependencies
31+
run: |
32+
python -m pip install --upgrade pip
33+
python -m pip install ruff
34+
python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
35+
pip install --progress-bar off .
36+
- name: Lint with Ruff
37+
run: |
38+
ruff check typex

.github/workflows/testing.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: "TESTING"
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
pull_request:
8+
branches:
9+
- "*"
10+
11+
jobs:
12+
build:
13+
14+
runs-on: ${{ matrix.os }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest]
19+
python-version: [3.9, 3.12]
20+
21+
steps:
22+
- name: Checkout repository
23+
uses: actions/checkout@v4
24+
with:
25+
fetch-depth: 0
26+
- name: Set up Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v5
28+
with:
29+
python-version: ${{ matrix.python-version }}
30+
- name: Install dependencies
31+
run: |
32+
sudo apt-get install -y git-lfs
33+
python -m pip install --upgrade pip
34+
python -m pip install pynose coverage coveralls
35+
python -m pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu
36+
python -m pip install --progress-bar off .[plugins]
37+
- name: Run unit tests
38+
run: |
39+
nosetests --with-coverage --cover-package=typex --verbosity=2
40+
- name: Coveralls
41+
if: matrix.python-version == 3.9
42+
env:
43+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44+
run: |
45+
coveralls --service=github

LICENSE.rst

Lines changed: 0 additions & 3 deletions
This file was deleted.

README.rst

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,23 @@ TypeX supplies a simple decorator to enforce Python types on function parameters
4141
Where to start
4242
--------------
4343

44-
Please find below a simple usage example:
44+
Please find below a simple usage example::
4545

46-
```python
47-
from typex import typecheck
4846

47+
from typex import typecheck
4948

50-
@typecheck
51-
def halve_integer(a: int) -> float:
52-
return a / 2
49+
@typecheck(hints_params=True, hints_return=True)
50+
def halve_integer(a: int) -> float:
51+
return a / 2
52+
53+
halve_integer(5) # 2.5
54+
halve_integer(5.0) # TraitsError
5355

54-
halve_integer(5) # 2.5
55-
halve_integer(5.0) # TraitsError
56-
```
5756

5857
Install
5958
-------
6059

61-
The package is available on `pypi <https://pypi.org/project/typex>`_.
60+
The PyPi package is available `online <https://pypi.org/project/typex>`_.
6261

6362

6463
Contributing
@@ -70,4 +69,4 @@ If you want to contribute to typex, be sure to review the `contribution guidelin
7069
License
7170
-------
7271

73-
This project is under the following `LICENSE <./LICENSE.rst>`_.
72+
This project is under the following `LICENSE <https://spdx.org/licenses/CECILL-B.html>`_.

pyproject.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
[build-system]
2-
requires = ["setuptools>=61.2"]
2+
requires = [
3+
"setuptools >= 61.2",
4+
]
35
build-backend = "setuptools.build_meta"
46

57
[project]
6-
name = "typex"
8+
name = "python-typex"
79
readme = "README.rst"
810
requires-python = ">=3.9"
911
authors = [
1012
{name = "typex developers", email = "antoine.grigis@cea.fr"},
1113
]
12-
license = {text = "CeCILL-B"}
14+
license = { text = "CECILL-B" }
1315
classifiers = [
14-
"Development Status :: 1 - Planning",
1516
"Environment :: Console",
1617
"Operating System :: OS Independent",
17-
"Programming Language :: Python",
18+
"Programming Language :: Python :: 3",
1819
"Topic :: Scientific/Engineering",
1920
]
2021
dependencies = [

typex/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@
66
# for details.
77
##########################################################################
88

9-
__version__ = "0.0.1"
9+
__version__ = "0.0.2"
10+
11+
from .decorator import typecheck
12+
from .validation import check_type

typex/decorator.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
##########################################################################
2+
# NSAp - Copyright (C) CEA, 2025
3+
# Distributed under the terms of the CeCILL-B license, as published by
4+
# the CEA-CNRS-INRIA. Refer to the LICENSE file or to
5+
# http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
6+
# for details.
7+
##########################################################################
8+
9+
10+
import inspect
11+
from functools import wraps
12+
13+
from .validation import check_type
14+
15+
16+
def typecheck(hints_params=True, hints_return=False):
17+
""" Enforce type hints on the parameters and return types of the decorated
18+
function.
19+
20+
Parameters
21+
----------
22+
hints_params: bool, default=False
23+
Require all parameter type hints to be specified.
24+
hints_return: bool, default=False
25+
Require the return type hint to be specified.
26+
"""
27+
def decorator(func):
28+
checker = TypEx(
29+
func,
30+
hints_params=hints_params,
31+
hints_return=hints_return
32+
)
33+
@wraps(func)
34+
def wrapper(*args, **kwargs):
35+
checker.check_params(args, kwargs)
36+
result = func(*args, **kwargs)
37+
checker.check_output(result)
38+
return result
39+
return wrapper
40+
return decorator
41+
42+
43+
class TypEx:
44+
""" Enforce type hints on a function.
45+
46+
Parameters
47+
----------
48+
func: Callable
49+
The function whose type hints should be enforced.
50+
hints_params: bool, default=False
51+
Require all parameter type hints to be specified.
52+
hints_return: bool, default=False
53+
Require the return type hint to be specified.
54+
55+
Raises
56+
------
57+
TypeError
58+
If function parameters are not annotated.
59+
TraitError
60+
If the input value have incorrect type.
61+
NotImplementedError
62+
If a type is not handled by the code.
63+
"""
64+
def __init__(self, func, hints_params=False, hints_return=False):
65+
self.func = func
66+
self.hints_params = hints_params
67+
self.hints_return = hints_return
68+
self.annotations = self.func.__annotations__
69+
param_names = list(inspect.signature(func).parameters.keys())
70+
self.ignore_self = (len(param_names) > 0 and param_names[0] == "self")
71+
self.n_params = (len(self.annotations)
72+
if "return" not in self.annotations
73+
else len(self.annotations) - 1)
74+
if self.hints_return and "return" not in self.annotations:
75+
raise TypeError(
76+
f"The return outputs of the '{func.__name__}' function is not "
77+
"annotated."
78+
)
79+
80+
def check_params(self, passed_args, passed_kwargs):
81+
""" Check input function parameters.
82+
83+
Parameters
84+
----------
85+
passed_args: list
86+
List of args passed to the function
87+
passed_kwargs: dict
88+
Dict of kwargs passed to the function
89+
90+
Raises
91+
------
92+
TypeError
93+
If an input parameter is not valid.
94+
"""
95+
if self.n_params < len(passed_args):
96+
raise TypeError(
97+
"Unexpected number of parameters for the "
98+
f"{self.func.__name__} function."
99+
)
100+
named_args = list(self.annotations.keys())[:len(passed_args)]
101+
if self.ignore_self:
102+
passed_args = passed_args[1:]
103+
for name, val in zip(named_args, passed_args):
104+
check_type(val, self.func, name)
105+
for name, val in passed_kwargs.items():
106+
check_type(val, self.func, name)
107+
108+
def check_output(self, value):
109+
""" Check return value of the function call.
110+
111+
Parameters
112+
----------
113+
value: Any
114+
The return value of the function.
115+
"""
116+
check_type(value, self.func, "return")

0 commit comments

Comments
 (0)