Skip to content

Commit b5df554

Browse files
committed
chore: update infrastructure
1 parent 18bb29f commit b5df554

File tree

7 files changed

+334
-129
lines changed

7 files changed

+334
-129
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
name: 'Package - Build'
3+
run-name: 'Package: Build from ${{github.ref}}'
4+
5+
6+
on:
7+
workflow_dispatch:
8+
push:
9+
paths:
10+
- 'pyproject.toml'
11+
12+
jobs:
13+
14+
publish:
15+
runs-on: ubuntu-latest
16+
permissions:
17+
id-token: write
18+
environment:
19+
name: PyPI
20+
steps:
21+
- name: 'Checkout repository from ${{github.ref}}'
22+
uses: actions/checkout@v3
23+
24+
- name: 'Build sdist'
25+
run: |
26+
pipx run build --sdist --wheel --outdir dist/
27+
28+
- name: 'Upload package'
29+
uses: pypa/gh-action-pypi-publish@release/v1
30+
# https://github.com/marketplace/actions/pypi-publish
31+
with:
32+
packages-dir: dist
33+
verify-metadata: false
34+
verbose: true
35+
print-hash: true
36+
skip-existing: false

.gitignore

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Ref: https://github.com/github/gitignore
2+
3+
# Byte-compiled / optimized / DLL files
4+
__pycache__/
5+
*.py[cod]
6+
*$py.class
7+
8+
# C extensions
9+
*.so
10+
11+
# Distribution / packaging
12+
.Python
13+
build/
14+
develop-eggs/
15+
dist/
16+
downloads/
17+
eggs/
18+
.eggs/
19+
lib/
20+
lib64/
21+
parts/
22+
sdist/
23+
var/
24+
wheels/
25+
share/python-wheels/
26+
*.egg-info/
27+
.installed.cfg
28+
*.egg
29+
30+
# PyInstaller
31+
# Usually these files are written by a python script from a template
32+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
33+
*.manifest
34+
*.spec
35+
36+
# Installer logs
37+
pip-log.txt
38+
pip-delete-this-directory.txt
39+
40+
# Unit test / coverage reports
41+
htmlcov/
42+
.tox/
43+
.nox/
44+
.coverage
45+
.coverage.*
46+
.cache
47+
nosetests.xml
48+
coverage.xml
49+
*.cover
50+
.hypothesis/
51+
.pytest_cache
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/website/_build/
73+
docs/website/source/api/_autosummary
74+
75+
# PyBuilder
76+
.pybuilder/
77+
target/
78+
79+
# Jupyter Notebook
80+
.ipynb_checkpoints
81+
82+
# IPython
83+
profile_default/
84+
ipython_config.py
85+
86+
# pyenv
87+
.python-version
88+
89+
# Celery stuff
90+
celerybeat-schedule
91+
celerybeat.pid
92+
93+
# SageMath parsed files
94+
*.sage.py
95+
96+
# Environments
97+
.env
98+
.venv
99+
env/
100+
venv/
101+
ENV/
102+
env.bak/
103+
venv.bak/
104+
105+
# Spyder project settings
106+
.spyderproject
107+
.spyproject
108+
109+
# Rope project settings
110+
.ropeproject
111+
112+
# mkdocs documentation
113+
/site
114+
115+
# mypy
116+
.mypy_cache/
117+
.dmypy.json
118+
dmypy.json
119+
120+
# Pyre type checker
121+
# There are reports this comes from LLVM profiling, but also Xcode 9.
122+
.pyre/
123+
124+
# pytype static type analyzer
125+
.pytype/
126+
127+
# Cython debug symbols
128+
cython_debug/
129+
130+
# profraw files from LLVM? Unclear exactly what triggers this
131+
# There are reports this comes from LLVM profiling, but also Xcode 9.
132+
*profraw
133+
134+
# In-tree generated files
135+
*/_version.py
136+
137+
# VSCode
138+
.vscode/
139+
140+
# PyCharm
141+
.idea/
142+
143+
# MacOS system files
144+
.DS_Store
145+
146+
# PyPackIT
147+
data/_local_reports/
148+
data/_cache/
149+
!/dev/build/

pyproject.toml

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1+
12
[build-system]
2-
requires = ["setuptools>=61.0", "versioningit~=2.0"]
3+
requires = ["setuptools>=61.0", "versioningit"]
34
build-backend = "setuptools.build_meta"
45

5-
[project]
6-
# --- Project Metadata ---
7-
# References:
8-
# PEP 621:
9-
# https://peps.python.org/pep-0621/
10-
# Python Packaging User Guide - Declaring project metadata:
11-
# https://packaging.python.org/en/latest/specifications/declaring-project-metadata/
12-
name = "pycolorit"
13-
version = "0.0.0"
146

15-
dependencies = [
16-
'numpy',
17-
]
7+
# ----------------------------------------- setuptools -------------------------------------------
8+
[tool.setuptools]
9+
include-package-data = true
10+
zip-safe = false
1811

1912
[tool.setuptools.packages.find]
20-
namespaces = false
13+
where = ["src"]
14+
namespaces = true
15+
16+
17+
# ----------------------------------------- Project Metadata -------------------------------------
18+
#
19+
[project]
20+
version = "0.0.0.dev0"
21+
name = "pycolorit"
22+
requires-python = ">=3.9"
23+
dependencies = [
24+
"numpy",
25+
"jax",
26+
]
Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,90 @@
1-
from typing import Sequence, Literal
1+
# Standard libraries
2+
from typing import Literal, Sequence
3+
4+
# Non-standard libraries
25
import jax
3-
import numpy as np
46
import jax.numpy as jnp
7+
import numpy as np
58

69

710
class Color:
8-
911
def __init__(self, values: np.ndarray):
1012
self._values = values
1113
return
1214

1315
def rgb(
14-
self,
15-
unit: Literal['int', 'percent', 'fraction'] = 'int',
16-
as_str: bool = False,
17-
with_comma: bool = True
16+
self,
17+
unit: Literal["int", "percent", "fraction"] = "int",
18+
as_str: bool = False,
19+
with_comma: bool = True,
1820
) -> np.ndarray:
1921
rgb_colors = self._values
20-
sep = ', ' if with_comma else ' '
21-
if unit == 'int':
22+
sep = ", " if with_comma else " "
23+
if unit == "int":
2224
if not as_str:
2325
return rgb_colors
24-
return np.array([f'rgb({sep.join(color)})' for color in rgb_colors.astype(str)])
26+
return np.array([f"rgb({sep.join(color)})" for color in rgb_colors.astype(str)])
2527
rgb_colors_norm = rgb_colors / 255
26-
if unit == 'percent':
28+
if unit == "percent":
2729
rgb_colors_norm *= 100
2830
if not as_str:
2931
return rgb_colors_norm
3032
return np.array(
3133
[
32-
f'rgb({sep.join(color)})'
33-
for color in np.char.mod("%.1f%%" if unit == 'percent' else "%.1f", rgb_colors_norm)
34+
f"rgb({sep.join(color)})"
35+
for color in np.char.mod("%.1f%%" if unit == "percent" else "%.1f", rgb_colors_norm)
3436
]
3537
)
3638

3739
def hex(
38-
self,
39-
with_hash: bool = False,
40+
self,
41+
with_hash: bool = False,
4042
):
41-
sep = '#' if with_hash else ''
42-
return np.array([f'{sep}{rgb[0]:02X}{rgb[1]:02X}{rgb[2]:02X}' for rgb in self._values])
43+
sep = "#" if with_hash else ""
44+
return np.array([f"{sep}{rgb[0]:02X}{rgb[1]:02X}{rgb[2]:02X}" for rgb in self._values])
4345

4446
@property
4547
def hsl(self):
4648
return _rgb_to_hsl(rgb=self._values)
4749

4850

49-
5051
def rgb(values: tuple[int, int, int] | Sequence[tuple[int, int, int]]):
5152
colors = np.asarray(values)
5253
if not np.issubdtype(colors.dtype, np.integer):
53-
raise ValueError(f"`values` must be a sequence of integers, but found elements with type {colors.dtype}")
54+
raise ValueError(
55+
f"`values` must be a sequence of integers, but found elements with type {colors.dtype}"
56+
)
5457
if np.any(np.logical_or(colors < 0, colors > 255)):
5558
raise ValueError("`values` must be in the range [0, 255].")
5659
colors = colors.astype(np.ubyte)
5760
if colors.ndim > 2 or colors.shape[-1] != 3:
58-
raise ValueError(f"`values` must either have a shape of (3, ) or (n, 3). The input shape was {colors.shape}")
61+
raise ValueError(
62+
f"`values` must either have a shape of (3, ) or (n, 3). The input shape was {colors.shape}"
63+
)
5964
colors = colors[np.newaxis] if colors.ndim == 1 else colors
6065
return Color(values=colors)
6166

6267

6368
def hexa(values: str | Sequence[str]) -> Color:
64-
6569
def process_single_hex(val: str) -> tuple[int, int, int]:
6670
if len(val) == 3:
67-
val = ''.join([d * 2 for d in val])
71+
val = "".join([d * 2 for d in val])
6872
elif len(val) != 6:
6973
raise ValueError(f"Hex color '{val}' not recognized.")
70-
return tuple(int(val[i:i + 2], 16) for i in range(0, 5, 2))
74+
return tuple(int(val[i : i + 2], 16) for i in range(0, 5, 2))
7175

7276
colors = np.asarray(values)
7377
if not np.issubdtype(colors.dtype, np.str_):
74-
raise ValueError(f"`values` must be a sequence of strings, but found elements with type {colors.dtype}")
78+
raise ValueError(
79+
f"`values` must be a sequence of strings, but found elements with type {colors.dtype}"
80+
)
7581
if colors.ndim == 0:
7682
colors = colors[np.newaxis]
7783
elif colors.ndim > 1:
7884
raise ValueError(
7985
f"`values` must either be a string, or a 1-dimensional sequence. The input dimension was {colors.ndim}"
8086
)
81-
colors = np.char.lstrip(colors, '#')
87+
colors = np.char.lstrip(colors, "#")
8288
colors_rgb = np.empty(shape=(colors.size, 3), dtype=np.ubyte)
8389
for i, color in enumerate(colors):
8490
colors_rgb[i] = process_single_hex(color)
@@ -112,19 +118,18 @@ def _rgb_to_hsl(rgb: jax.Array):
112118
# 'H' and 'S' values are non-zero and must be calculated.
113119
# Calculate all 'S' values.
114120
# Here conditioning on `minmax_dist == 0` is not necessary, since the numerator is `minmax_dist`
115-
hsl_s = jnp.where(
116-
hsl_l > 0.5,
117-
minmax_dist / (2 - minmax_sum),
118-
minmax_dist / minmax_sum
119-
)
121+
hsl_s = jnp.where(hsl_l > 0.5, minmax_dist / (2 - minmax_sum), minmax_dist / minmax_sum)
120122
# Calculate all 'H' values.
121-
hsl_h = jnp.where(
122-
minmax_dist == 0,
123-
0,
123+
hsl_h = (
124124
jnp.where(
125-
max_val == rs,
126-
jnp.where(gs < bs, 6, 0) + (gs - bs) / minmax_dist,
127-
jnp.where(max_val == gs, (bs - rs) / minmax_dist + 2, (rs - gs) / minmax_dist + 4)
125+
minmax_dist == 0,
126+
0,
127+
jnp.where(
128+
max_val == rs,
129+
jnp.where(gs < bs, 6, 0) + (gs - bs) / minmax_dist,
130+
jnp.where(max_val == gs, (bs - rs) / minmax_dist + 2, (rs - gs) / minmax_dist + 4),
131+
),
128132
)
129-
) / 6
133+
/ 6
134+
)
130135
return jnp.stack([hsl_h, hsl_s, hsl_l], axis=-1) * 100

0 commit comments

Comments
 (0)