Skip to content

Commit

Permalink
Use colour checks configuration.
Browse files Browse the repository at this point in the history
KelSolaar committed Jan 12, 2025
1 parent 4d4133b commit d8bc035
Showing 23 changed files with 1,386 additions and 788 deletions.
34 changes: 17 additions & 17 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
# Common Files
*.egg-info
*.pyc
*.pyo
.DS_Store
.coverage*
.fleet
.idea
.ipynb_checkpoints
.vs
.vscode
.sandbox
uv.lock

__pycache__
# Common Directories
.fleet/
.idea/
.ipynb_checkpoints/
.python-version
.vs/
.vscode/
.sandbox/
build/
dist/
docs/_build/
docs/generated/
node_modules/
references/

build
dist
docs/_build
docs/_static/Basics_*.png
docs/_static/Examples_*.png
docs/_static/Plotting_*.png
docs/_static/Tutorial_*.png
docs/generated
poetry.lock
references
__pycache__
22 changes: 8 additions & 14 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.5.0"
rev: "v5.0.0"
hooks:
- id: check-added-large-files
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-yaml
exclude: config-aces-reference.ocio.yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
@@ -16,35 +15,30 @@ repos:
- id: requirements-txt-fixer
- id: trailing-whitespace
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
rev: v2.3.0
hooks:
- id: codespell
args: ["--ignore-words-list=co-ordinates,exitance,fro,hart,ist"]
exclude: "BIBLIOGRAPHY.bib|CONTRIBUTORS.rst"
- repo: https://github.com/ikamensh/flynt
rev: "1.0.1"
hooks:
- id: flynt
args: [--verbose]
args: ["--ignore-words-list=socio-economic"]
exclude: "BIBLIOGRAPHY.bib|CONTRIBUTORS.rst|.*.ipynb"
- repo: https://github.com/PyCQA/isort
rev: "5.13.2"
hooks:
- id: isort
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.1.14"
rev: "v0.8.2"
hooks:
- id: ruff-format
- id: ruff
args: [--fix]
- repo: https://github.com/adamchainz/blacken-docs
rev: 1.16.0
rev: 1.19.1
hooks:
- id: blacken-docs
language_version: python3.10
- repo: https://github.com/pre-commit/mirrors-prettier
rev: "v3.1.0"
rev: "v4.0.0-alpha.8"
hooks:
- id: prettier
exclude: config-aces-reference.ocio.yaml
- repo: https://github.com/pre-commit/pygrep-hooks
rev: "v1.10.0"
hooks:
4 changes: 2 additions & 2 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ If you would like to contribute to **Colour**, please refer to the following gui
About
-----

| **Colour** by Colour Developers
| Copyright 2013 Colour Developers – `colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| **Colour - CLF IO** by Colour Developers
| Copyright 2024 Colour Developers – `colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause
| `https://github.com/colour-science/colour <https://github.com/colour-science/colour>`__
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2013 Colour Developers
Copyright 2024 Colour Developers

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
@@ -167,6 +167,6 @@ About
-----

| **Colour - CLF IO** by Colour Developers
| Copyright 2015 Colour Developers – `colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| Copyright 2024 Colour Developers – `colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause
| `https://github.com/colour-science/colour-clf-io <https://github.com/colour-science/colour-clf-io>`__
119 changes: 66 additions & 53 deletions colour_clf_io/__init__.py
Original file line number Diff line number Diff line change
@@ -18,55 +18,17 @@

from __future__ import annotations

__application_name__ = "Colour - CLF IO"

__major_version__ = "0"
__minor_version__ = "0"
__change_version__ = "0"
__version__ = ".".join((__major_version__, __minor_version__, __change_version__))
import typing

if typing.TYPE_CHECKING:
from pathlib import Path

# Security issues in lxml should be addressed and no longer be a concern:
# NOTE: Security issues in lxml should be addressed and no longer be a concern:
# https://discuss.python.org/t/status-of-defusedxml-and-recommendation-in-docs/34762/6

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
"read_clf",
"parse_clf",
"LUT1D",
"LUT3D",
"ProcessNode",
"ProcessList",
"Matrix",
"Range",
"Exponent",
"ExponentStyle",
"ExponentParams",
"ASC_CDL",
"ASC_CDL_Style",
"SatNode",
"SOPNode",
"Interpolation1D",
"Interpolation3D",
"BitDepth",
"Channel",
"CalibrationInfo",
"Info",
"LogParams",
"LogStyle",
"RangeStyle",
"Log",
]

import lxml.etree

from .elements import (
Array,
CalibrationInfo,
ExponentParams,
ExponentStyle,
@@ -88,10 +50,61 @@
ProcessNode,
Range,
)
from .values import ASC_CDL_Style, BitDepth, Channel, Interpolation1D, Interpolation3D
from .values import (
ASC_CDL_Style,
BitDepth,
Channel,
Interpolation1D,
Interpolation3D,
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
"Array",
"CalibrationInfo",
"ExponentParams",
"ExponentStyle",
"Info",
"LogParams",
"LogStyle",
"RangeStyle",
"SatNode",
"SOPNode",
]
__all__ += ["ProcessList"]
__all__ += [
"ASC_CDL",
"LUT1D",
"LUT3D",
"Exponent",
"Log",
"Matrix",
"ProcessNode",
"Range",
]
__all__ += [
"ASC_CDL_Style",
"BitDepth",
"Channel",
"Interpolation1D",
"Interpolation3D",
]

__application_name__ = "Colour - CLF IO"

def read_clf(path) -> ProcessList:
__major_version__ = "0"
__minor_version__ = "1"
__change_version__ = "0"
__version__ = f"{__major_version__}.{__minor_version__}.{__change_version__}"


def read_clf(path: str | Path) -> ProcessList | None:
"""
Read given *CLF* file and return the resulting `ProcessList`.
@@ -108,15 +121,15 @@ def read_clf(path) -> ProcessList:
------
:class: ParsingError
If the given file does not contain a valid CLF document.
"""
xml = lxml.etree.parse(path) # noqa: S320

xml = lxml.etree.parse(str(path)) # noqa: S320
xml_process_list = xml.getroot()
root = ProcessList.from_xml(xml_process_list)
return root

return ProcessList.from_xml(xml_process_list)


def parse_clf(text):
def parse_clf(text: str | bytes) -> ProcessList | None:
"""
Read given string as a *CLF* document and return the resulting `ProcessList`.
@@ -133,8 +146,8 @@ def parse_clf(text):
------
:class: ParsingError
If the given string does not contain a valid CLF document.
"""

xml = lxml.etree.fromstring(text) # noqa: S320
root = ProcessList.from_xml(xml)
return root

return ProcessList.from_xml(xml)
260 changes: 173 additions & 87 deletions colour_clf_io/elements.py

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions colour_clf_io/errors.py
Original file line number Diff line number Diff line change
@@ -3,18 +3,22 @@
======
Defines errors that are used as part of the parsing and validation of CLF documents.
"""

from __future__ import annotations

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
"ParsingError",
"ValidationError",
]


class ParsingError(Exception):
"""
255 changes: 154 additions & 101 deletions colour_clf_io/parsing.py

Large diffs are not rendered by default.

48 changes: 28 additions & 20 deletions colour_clf_io/process_list.py
Original file line number Diff line number Diff line change
@@ -3,7 +3,6 @@
============
Defines the top level Process List object that represents a CLF process.
"""

from __future__ import annotations
@@ -28,13 +27,13 @@
)

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__ALL__ = ["ProcessList"]
__all__ = ["ProcessList"]


@dataclass
@@ -44,7 +43,7 @@ class ProcessList:
References
----------
https://docs.acescentral.com/specifications/clf/#processList
- https://docs.acescentral.com/specifications/clf/#processList
"""

id: str
@@ -61,30 +60,37 @@ class ProcessList:
info: Info | None

@staticmethod
def from_xml(xml):
def from_xml(xml: lxml.etree._Element | None) -> ProcessList | None:
"""
Parse and return the Process List from the given XML node. Returns None if the
given element is None.
Parse and return a :class:`colour_clf_io.ProcessList` class instance
from the given XML node. Returns `None`` if the given XML node is ``None``.
Expects the xml element to be a valid element according to the CLF
specification.
Returns
-------
class:`colour_clf_io.ProcessList` or :py:data:`None`
Parsed XML node.
Raises
------
:class: ParsingError
If the node does not conform to the specification, a `ParsingError`
will be raised. The error message will indicate the details of the issue
that was encountered.
:class:`ParsingError`
If the node does not conform to the specification, a ``ParsingError``
exception will be raised. The error message will indicate the
details of the issue that was encountered.
"""

if xml is None:
return None
id = xml.get("id") # noqa: A001
must_have(id, "ProcessList must contain an `id` attribute")

id_ = xml.get("id")
must_have(id_, "ProcessList must contain an `id` attribute")

compatible_clf_version = xml.get("compCLFversion")
must_have(
compatible_clf_version,
"ProcessList must contain an `compCLFversion` attribute",
'ProcessList must contain a "compCLFversion" attribute',
)

# By default, we would expect the correct namespace as per the specification.
@@ -95,9 +101,9 @@ def from_xml(xml):
if not namespace:
config.namespace_name = None
elif namespace != config.namespace_name:
raise ParsingError(
f"Found invalid xmlns attribute in process list: {namespace}"
)
exception = f"Found invalid xmlns attribute in process list: {namespace}"

raise ParsingError(exception)

name = xml.get("name")
inverse_of = xml.get("inverseOf")
@@ -111,16 +117,18 @@ def from_xml(xml):
process_nodes = filter(
lambda node: lxml.etree.QName(node).localname not in ignore_nodes, xml
)

if not process_nodes:
warn("Got empty process node.")

process_nodes = [
parse_process_node(xml_node, config) for xml_node in process_nodes
]
assert_bit_depth_compatibility(process_nodes)

return ProcessList(
id=id,
compatible_CLF_version=compatible_clf_version,
id=id_, # pyright: ignore
compatible_CLF_version=compatible_clf_version, # pyright: ignore
process_nodes=process_nodes,
name=name,
inverse_of=inverse_of,
357 changes: 232 additions & 125 deletions colour_clf_io/process_nodes.py

Large diffs are not rendered by default.

24 changes: 20 additions & 4 deletions colour_clf_io/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import pytest # noqa: D100
"""
Pytest Configuration
====================
Configure *pytest* to use with *OpenColorIO* if available.
"""

from __future__ import annotations

import pytest


def pytest_addoption(parser) -> None: # noqa: ANN001
"""Add a *pytest* option for test requiring *OpenColorIO*."""

def pytest_addoption(parser): # noqa: D103
parser.addoption(
"--with_ocio",
action="store_true",
@@ -10,15 +21,20 @@ def pytest_addoption(parser): # noqa: D103
)


def pytest_configure(config): # noqa: D103
def pytest_configure(config) -> None: # noqa: ANN001
"""Configure *pytest* for *OpenColorIO*."""

config.addinivalue_line(
"markers", "with_ocio: mark test that require the OpenColorIO library"
)


def pytest_collection_modifyitems(config, items): # noqa: D103
def pytest_collection_modifyitems(config, items) -> None: # noqa: ANN001
"""Modify *pytest* collection for *OpenColorIO*."""

if config.getoption("--with_ocio"):
return

skip_slow = pytest.mark.skip(reason="need --with_ocio option to run")
for item in items:
if "with_ocio" in item.keywords:
101 changes: 85 additions & 16 deletions colour_clf_io/tests/test_clf_common.py
Original file line number Diff line number Diff line change
@@ -2,64 +2,133 @@
Defines helper functionality for CLF tests.
"""

from __future__ import annotations

import os
import tempfile

import numpy as np
import numpy.typing as npt

import colour_clf_io.parsing
import colour_clf_io.process_list

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = ["snippet_to_process_list", "wrap_snippet"]
__all__ = [
"EXAMPLE_WRAPPER",
"wrap_snippet",
"snippet_to_process_list",
"snippet_as_tmp_file",
"result_as_array",
]


EXAMPLE_WRAPPER = """<?xml version="1.0" ?>
EXAMPLE_WRAPPER: str = """
<?xml version="1.0" ?>
<ProcessList id="Example Wrapper" compCLFversion="3.0" xmlns="urn:AMPAS:CLF:v3.0">
{0}
</ProcessList>
"""
""".strip()


def wrap_snippet(snippet: str) -> str:
"""# noqa: D401
Takes a string that should contain the text representation of a CLF node, and
"""
Take a string that should contain the text representation of a CLF node, and
returns valid CLF document. Essentially the given string is pasted into the
`ProcessList` if a CLF document.
This is useful to quickly convert example snippets of Process Nodes into valid CLF
documents for parsing.
Parameters
----------
snippet
Snippet to wrap as a CLF document.
Returns
-------
:class:`str`
CLF document.
"""

return EXAMPLE_WRAPPER.format(snippet)


def snippet_to_process_list(snippet: str) -> colour_clf_io.process_list.ProcessList:
"""# noqa: D401
Takes a string that should contain a valid body for a XML Process List and
returns the parsed `ProcessList`.
def snippet_to_process_list(
snippet: str,
) -> colour_clf_io.process_list.ProcessList | None:
"""
Take a string that should contain a valid body for an XML Process List and
returns the parsed :class:`colour_clf_io.process_list.ProcessList` class
instance.
Parameters
----------
snippet
Snippet to parse.
Returns
-------
:class:`colour_clf_io.process_list.ProcessList`
"""

doc = wrap_snippet(snippet)

return colour_clf_io.parse_clf(doc)


def snippet_as_tmp_file(snippet):
def snippet_as_tmp_file(snippet: str) -> str:
"""
Write given snippet to a temporary file.
Parameters
----------
snippet
Snippet to write
Returns
-------
:class:`str`
Temporary filename.
"""

doc = wrap_snippet(snippet)
tmp_folder = tempfile.gettempdir()
file_name = os.path.join(tmp_folder, "colour_snippet.clf")
with open(file_name, "w") as f:
f.write(doc)

with open(file_name, "w") as clf_file:
clf_file.write(doc)

return file_name


def result_as_array(result_text):
result_parts = result_text.decode("utf-8").strip().split()
def result_as_array(result: bytes) -> npt.NDArray:
"""
Decode given result and convert them to an array.
Parameters
----------
result
Result to convert to an array.
Returns
-------
:class:`np.ndarray`
Converted result array.
"""

result_parts = result.decode("utf-8").strip().split()
if len(result_parts) != 3:
raise RuntimeError(f"Invalid OCIO result: {result_text}")
exception = f"Invalid OCIO result: {result}"

raise RuntimeError(exception)

result_values = list(map(float, result_parts))

return np.array(result_values)
469 changes: 287 additions & 182 deletions colour_clf_io/tests/test_clf_parsing.py

Large diffs are not rendered by default.

54 changes: 28 additions & 26 deletions colour_clf_io/values.py
Original file line number Diff line number Diff line change
@@ -2,9 +2,8 @@
Values
=======
Defines enums that represent allowed values in some of the fields contained in a CLF
document.
Defines enums that represent allowed values in some of the fields contained in a
CLF document.
"""

from __future__ import annotations
@@ -13,13 +12,13 @@
from enum import Enum

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__ALL__ = [
__all__ = [
"BitDepth",
"Channel",
"Interpolation1D",
@@ -34,7 +33,7 @@ class BitDepth(Enum):
References
----------
https://docs.acescentral.com/specifications/clf/#processNode
- https://docs.acescentral.com/specifications/clf/#processNode
"""

i8 = "8i"
@@ -44,13 +43,13 @@ class BitDepth(Enum):
f16 = "16f"
f32 = "32f"

def scale_factor(self):
"""Return the scale factor that is needed to normalise a value of the given
BitDepth to the range 0..1.
def scale_factor(self) -> float:
"""
Return the scale factor that is needed to normalise a value of the given
bit depth to the range 0..1.
Examples
--------
```
>>> from colour_clf_io.values import BitDepth
>>> 255 / BitDepth.i8.scale_factor() == 1.0
True
@@ -60,34 +59,37 @@ def scale_factor(self):
True
>>> 1.0 / BitDepth.f16.scale_factor() == 1.0
True
```
"""

if self == BitDepth.i8:
return 2**8 - 1
elif self == BitDepth.i10:

if self == BitDepth.i10:
return 2**10 - 1
elif self == BitDepth.i12:

if self == BitDepth.i12:
return 2**12 - 1
elif self == BitDepth.i16:

if self == BitDepth.i16:
return 2**16 - 1
elif self in [BitDepth.f16, BitDepth.f32]:

if self in [BitDepth.f16, BitDepth.f32]:
return 1.0
raise NotImplementedError()

raise NotImplementedError

@classmethod
def all(cls):
"""Return a list of all valid BitDepth values.
def all(cls: type[BitDepth]) -> list:
"""
Return a list of all valid bit depth values.
Examples
--------
```
>>> from colour_clf_io.values import BitDepth
>>> BitDepth.all()
['8i', '10i', '12i', '16i', '16f', '32f']
```
"""

return [e.value for e in cls]


@@ -97,7 +99,7 @@ class Channel(enum.Enum):
References
----------
https://docs.acescentral.com/specifications/clf/#ranges
- https://docs.acescentral.com/specifications/clf/#ranges
"""

R = "R"
@@ -111,7 +113,7 @@ class Interpolation1D(Enum):
References
----------
https://docs.acescentral.com/specifications/clf/#lut1d
- https://docs.acescentral.com/specifications/clf/#lut1d
"""

LINEAR = "linear"
@@ -123,7 +125,7 @@ class Interpolation3D(Enum):
References
----------
https://docs.acescentral.com/specifications/clf/#lut3d
- https://docs.acescentral.com/specifications/clf/#lut3d
"""

TRILINEAR = "trilinear"
@@ -136,7 +138,7 @@ class ASC_CDL_Style(enum.Enum):
References
----------
https://docs.acescentral.com/specifications/clf/#asc_cdl
- https://docs.acescentral.com/specifications/clf/#asc_cdl
"""

FWD = "Fwd"
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@

sys.path.append(str(Path(__file__).parent.parent))

import colour_clf_io as package # noqa: E402
import colour_clf_io as package

basename = re.sub("_(\\w)", lambda x: x.group(1).upper(), package.__name__.title())

2 changes: 1 addition & 1 deletion docs/index.rst
Original file line number Diff line number Diff line change
@@ -71,6 +71,6 @@ About
-----

| **Colour - CLF IO** by Colour Developers
| Copyright 2015 Colour Developers – `colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| Copyright 2024 Colour Developers – `colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| This software is released under terms of BSD-3-Clause: https://opensource.org/licenses/BSD-3-Clause
| `https://github.com/colour-science/colour-clf-io <https://github.com/colour-science/colour-clf-io>`__
21 changes: 10 additions & 11 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -5,28 +5,27 @@ alabaster==1.0.0
babel==2.16.0
beautifulsoup4==4.12.3
biblib-simple==0.1.2
certifi==2024.8.30
charset-normalizer==3.4.0
certifi==2024.12.14
charset-normalizer==3.4.1
colorama==0.4.6 ; sys_platform == 'win32'
docutils==0.21.2
idna==3.10
imagesize==1.4.1
jinja2==3.1.4
jinja2==3.1.5
latexcodec==3.0.0
lxml==5.3.0
lxml-stubs==0.5.1
markupsafe==3.0.2
numpy==2.1.2
numpy==2.2.1
packaging==24.2
pybtex==0.24.0
pybtex-docutils==1.0.3
pydata-sphinx-theme==0.16.0
pygments==2.18.0
pydata-sphinx-theme==0.16.1
pygments==2.19.1
pyyaml==6.0.2
requests==2.32.3
restructuredtext-lint==1.4.0
setuptools==75.3.0 ; python_full_version >= '3.12'
six==1.16.0
setuptools==75.8.0 ; python_full_version >= '3.12'
six==1.17.0
snowballstemmer==2.2.0
soupsieve==2.6
sphinx==8.1.3
@@ -37,6 +36,6 @@ sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
tomli==2.0.2 ; python_full_version < '3.11'
tomli==2.2.1 ; python_full_version < '3.11'
typing-extensions==4.12.2
urllib3==2.2.3
urllib3==2.3.0
145 changes: 51 additions & 94 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -45,10 +45,8 @@ dependencies = [
"numpy>=1.24,<3",
"typing-extensions>=4,<5",
"lxml>=5.2.1,<6",
"lxml-stubs>=0.5.1,<0.6"
]


[project.optional-dependencies]
docs = [
"biblib-simple",
@@ -58,7 +56,6 @@ docs = [
"sphinxcontrib-bibtex",
]


[tool.uv]
package = true
dev-dependencies = [
@@ -74,6 +71,8 @@ dev-dependencies = [
"pytest-xdist",
"toml",
"twine",
# Package Specific
"lxml-stubs>=0.5.1,<0.6"
]

[build-system]
@@ -84,19 +83,15 @@ build-backend = "hatchling.build"
packages = [ "colour-clf-io" ]

[tool.codespell]
ignore-words-list = 'co-ordinates,exitance,hart,ist'
skip = 'BIBLIOGRAPHY.bib,CONTRIBUTORS.rst'

[tool.flynt]
line_length = 999
ignore-words-list = "socio-economic"
skip = "BIBLIOGRAPHY.bib,CONTRIBUTORS.rst,*.ipynb"

[tool.isort]
ensure_newline_before_comments = true
force_grid_wrap = 0
include_trailing_comma = true
line_length = 88
multi_line_output = 3
skip_glob = ["colour/**/__init__.py"]
split_on_trailing_comma = true
use_parentheses = true

@@ -105,105 +100,67 @@ reportMissingImports = false
reportMissingModuleSource = false
reportUnboundVariable = false
reportUnnecessaryCast = true
reportUnnecessaryTypeIgnorComment = true
reportUnnecessaryTypeIgnoreComment = true
reportUnsupportedDunderAll = false
reportUnusedExpression = false


[tool.pytest.ini_options]
addopts = "--durations=5"

[tool.ruff]
target-version = "py39"
target-version = "py310"
line-length = 88
lint.select = [
"A", # flake8-builtins
"ARG", # flake8-unused-arguments
# "ANN", # flake8-annotations
"B", # flake8-bugbear
# "BLE", # flake8-blind-except
"C4", # flake8-comprehensions
# "C90", # mccabe
# "COM", # flake8-commas
"DTZ", # flake8-datetimez
"D", # pydocstyle
"E", # pydocstyle
# "ERA", # eradicate
# "EM", # flake8-errmsg
"EXE", # flake8-executable
"F", # flake8
# "FBT", # flake8-boolean-trap
"G", # flake8-logging-format
"I", # isort
"ICN", # flake8-import-conventions
"INP", # flake8-no-pep420
"ISC", # flake8-implicit-str-concat
"N", # pep8-naming
# "PD", # pandas-vet
"PIE", # flake8-pie
"PGH", # pygrep-hooks
"PL", # pylint
# "PT", # flake8-pytest-style
# "PTH", # flake8-use-pathlib [Enable]
"Q", # flake8-quotes
"RET", # flake8-return
"RUF", # Ruff
"S", # flake8-bandit
"SIM", # flake8-simplify
"T10", # flake8-debugger
"T20", # flake8-print
# "TCH", # flake8-type-checking
"TID", # flake8-tidy-imports
"TRY", # tryceratops
"UP", # pyupgrade
"W", # pydocstyle
"YTT", # flake8-2020
]
lint.ignore = [
"B008",
"B905",
"D104",
"D200",
"D202",
"D205",
"D301",
"D400",
"I001",
"N801",
"N802",
"N803",
"N806",
"N813",
"N815",
"N816",
"PGH003",
"PIE804",
"PLE0605",
"PLR0911",
"PLR0912",
"PLR0913",
"PLR0915",
"PLR2004",
"RET504",
"RET505",
"RET506",
"RET507",
"RET508",
"TRY003",
"TRY300",
select = ["ALL"]
ignore = [
"C", # Pylint - Convention
"C90", # mccabe
"COM", # flake8-commas
"ERA", # eradicate
"FBT", # flake8-boolean-trap
"FIX", # flake8-fixme
"PT", # flake8-pytest-style
"PTH", # flake8-use-pathlib [Enable]
"TD", # flake8-todos
"ANN401", # Dynamically typed expressions (typing.Any) are disallowed in `**kwargs`
"D200", # One-line docstring should fit on one line
"D202", # No blank lines allowed after function docstring
"D205", # 1 blank line required between summary line and description
"D301", # Use `r"""` if any backslashes in a docstring
"D400", # First line should end with a period
"I001", # Import block is un-sorted or un-formatted
"N801", # Class name `.*` should use CapWords convention
"N802", # Function name `.*` should be lowercase
"N803", # Argument name `.*` should be lowercase
"N806", # Variable `.*` in function should be lowercase
"N813", # Camelcase `.*` imported as lowercase `.*`
"N815", # Variable `.*` in class scope should not be mixedCase
"N816", # Variable `.*` in global scope should not be mixedCase
"NPY002", # Replace legacy `np.random.random` call with `np.random.Generator`
"PGH003", # Use specific rule codes when ignoring type issues
"PLR0912", # Too many branches
"PLR0913", # Too many arguments in function definition
"PLR0915", # Too many statements
"PLR2004", # Magic value used in comparison, consider replacing `.*` with a constant variable
"PYI036", # Star-args in `.*` should be annotated with `object`
"PYI051", # `Literal[".*"]` is redundant in a union with `str`
"PYI056", # Calling `.append()` on `__all__` may not be supported by all type checkers (use `+=` instead)
"RUF022", # [*] `__all__` is not sorted
"TRY003", # Avoid specifying long messages outside the exception class
"UP038", # Use `X | Y` in `isinstance` call instead of `(X, Y)`
]
lint.fixable = ["B", "C", "E", "F", "PIE", "RUF", "SIM", "UP", "W"]
typing-modules = []

[tool.ruff.lint.pydocstyle]
[tool.ruff.pydocstyle]
convention = "numpy"

[tool.ruff.lint.per-file-ignores]
[tool.ruff.per-file-ignores]
"__init__.py" = ["D104"]
"colour_hdri/examples/*" = ["INP", "T201", "T203"]
"docs/*" = ["INP"]
"tasks.py" = ["INP"]
"test_*" = ["S101"]
"utilities/*" = ["EXE001", "INP"]
"utilities/unicode_to_ascii.py" = ["RUF001"]

[tool.ruff.format]
docstring-code-format = true


[tool.setuptools]
packages = ["colour_clf_io"]
178 changes: 178 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# This file was autogenerated by uv via the following command:
# uv export --no-hashes --all-extras
accessible-pygments==0.0.5
alabaster==1.0.0
anyio==4.8.0
appnope==0.1.4 ; sys_platform == 'darwin'
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.3.0
asttokens==3.0.0
async-lru==2.0.4
attrs==24.3.0
babel==2.16.0
backports-tarfile==1.2.0 ; python_full_version < '3.12'
beautifulsoup4==4.12.3
biblib-simple==0.1.2
bleach==6.2.0
certifi==2024.12.14
cffi==1.17.1
cfgv==3.4.0
charset-normalizer==3.4.1
click==8.1.8
colorama==0.4.6 ; sys_platform == 'win32'
comm==0.2.2
coverage==7.6.10
coveralls==4.0.1
cryptography==44.0.0 ; sys_platform == 'linux'
debugpy==1.8.11
decorator==5.1.1
defusedxml==0.7.1
distlib==0.3.9
docopt==0.6.2
docutils==0.21.2
exceptiongroup==1.2.2 ; python_full_version < '3.11'
execnet==2.1.1
executing==2.1.0
fastjsonschema==2.21.1
filelock==3.16.1
fqdn==1.5.1
h11==0.14.0
hatch==1.14.0
hatchling==1.27.0
httpcore==1.0.7
httpx==0.28.1
hyperlink==21.0.0
identify==2.6.5
idna==3.10
imagesize==1.4.1
importlib-metadata==8.5.0 ; python_full_version < '3.12'
iniconfig==2.0.0
invoke==2.2.0
ipykernel==6.29.5
ipython==8.31.0
ipywidgets==8.1.5
isoduration==20.11.0
jaraco-classes==3.4.0
jaraco-context==6.0.1
jaraco-functools==4.1.0
jedi==0.19.2
jeepney==0.8.0 ; sys_platform == 'linux'
jinja2==3.1.5
json5==0.10.0
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter==1.1.1
jupyter-client==8.6.3
jupyter-console==6.6.3
jupyter-core==5.7.2
jupyter-events==0.11.0
jupyter-lsp==2.2.5
jupyter-server==2.15.0
jupyter-server-terminals==0.5.3
jupyterlab==4.3.4
jupyterlab-pygments==0.3.0
jupyterlab-server==2.27.3
jupyterlab-widgets==3.0.13
keyring==25.6.0
latexcodec==3.0.0
lxml==5.3.0
lxml-stubs==0.5.1
markdown-it-py==3.0.0
markupsafe==3.0.2
matplotlib-inline==0.1.7
mdurl==0.1.2
mistune==3.1.0
more-itertools==10.5.0
nbclient==0.10.2
nbconvert==7.16.5
nbformat==5.10.4
nest-asyncio==1.6.0
nh3==0.2.20
nodeenv==1.9.1
notebook==7.3.2
notebook-shim==0.2.4
numpy==2.2.1
overrides==7.7.0
packaging==24.2
pandocfilters==1.5.1
parso==0.8.4
pathspec==0.12.1
pexpect==4.9.0
pkginfo==1.12.0
platformdirs==4.3.6
pluggy==1.5.0
pre-commit==4.0.1
prometheus-client==0.21.1
prompt-toolkit==3.0.48
psutil==6.1.1
ptyprocess==0.7.0
pure-eval==0.2.3
pybtex==0.24.0
pybtex-docutils==1.0.3
pycparser==2.22
pydata-sphinx-theme==0.16.1
pygments==2.19.1
pyright==1.1.391
pytest==8.3.4
pytest-cov==6.0.0
pytest-xdist==3.6.1
python-dateutil==2.9.0.post0
python-json-logger==3.2.1
pywin32==308 ; platform_python_implementation != 'PyPy' and sys_platform == 'win32'
pywin32-ctypes==0.2.3 ; sys_platform == 'win32'
pywinpty==2.0.14 ; os_name == 'nt'
pyyaml==6.0.2
pyzmq==26.2.0
readme-renderer==44.0
referencing==0.35.1
requests==2.32.3
requests-toolbelt==1.0.0
restructuredtext-lint==1.4.0
rfc3339-validator==0.1.4
rfc3986==2.0.0
rfc3986-validator==0.1.1
rich==13.9.4
rpds-py==0.22.3
secretstorage==3.3.3 ; sys_platform == 'linux'
send2trash==1.8.3
setuptools==75.8.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
snowballstemmer==2.2.0
soupsieve==2.6
sphinx==8.1.3
sphinxcontrib-applehelp==2.0.0
sphinxcontrib-bibtex==2.6.3
sphinxcontrib-devhelp==2.0.0
sphinxcontrib-htmlhelp==2.1.0
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
stack-data==0.6.3
terminado==0.18.1
tinycss2==1.4.0
toml==0.10.2
tomli==2.2.1 ; python_full_version <= '3.11'
tomli-w==1.1.0
tomlkit==0.13.2
tornado==6.4.2
traitlets==5.14.3
trove-classifiers==2025.1.10.15
twine==6.0.1
types-python-dateutil==2.9.0.20241206
typing-extensions==4.12.2
uri-template==1.3.0
urllib3==2.3.0
userpath==1.9.2
uv==0.5.16
virtualenv==20.28.1
wcwidth==0.2.13
webcolors==24.11.1
webencodings==0.5.1
websocket-client==1.8.0
widgetsnbextension==4.0.13
zipp==3.21.0 ; python_full_version < '3.12'
zstandard==0.23.0
57 changes: 32 additions & 25 deletions tasks.py
Original file line number Diff line number Diff line change
@@ -13,19 +13,23 @@
import uuid
from itertools import chain
from textwrap import TextWrapper
from typing import Callable
from typing import TYPE_CHECKING

import biblib.bib
from invoke.context import Context
from invoke.tasks import task

import colour_clf_io

if TYPE_CHECKING:
from collections.abc import Callable

from invoke.context import Context

if not hasattr(inspect, "getargspec"):
inspect.getargspec = inspect.getfullargspec # pyright: ignore

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
@@ -73,7 +77,7 @@ def message_box(
width: int = 79,
padding: int = 3,
print_callable: Callable = print,
):
) -> None:
"""
Print a message inside a box.
@@ -123,7 +127,7 @@ def message_box(

ideal_width = width - padding * 2 - 2

def inner(text):
def inner(text: str) -> str:
"""Format and pads inner text for the message box."""

return (
@@ -148,7 +152,7 @@ def inner(text):


@task
def literalise(ctx: Context):
def literalise(ctx: Context) -> None:
"""
Write various literals in the `colour.hints` module.
@@ -171,7 +175,7 @@ def clean(
docs: bool = True,
bytecode: bool = False,
pytest: bool = True,
):
) -> None:
"""
Clean the project.
@@ -211,7 +215,7 @@ def formatting(
ctx: Context,
asciify: bool = True,
bibtex: bool = True,
):
) -> None:
"""
Convert unicode characters to ASCII and cleanup the *BibTeX* file.
@@ -254,7 +258,7 @@ def quality(
ctx: Context,
pyright: bool = True,
rstlint: bool = True,
):
) -> None:
"""
Check the codebase with *Pyright* and lints various *restructuredText*
files with *rst-lint*.
@@ -279,7 +283,7 @@ def quality(


@task
def precommit(ctx: Context):
def precommit(ctx: Context) -> None:
"""
Run the "pre-commit" hooks on the codebase.
@@ -294,7 +298,7 @@ def precommit(ctx: Context):


@task
def tests(ctx: Context):
def tests(ctx: Context) -> None:
"""
Run the unit tests with *Pytest*.
@@ -315,7 +319,7 @@ def tests(ctx: Context):


@task
def examples(ctx: Context, plots: bool = False):
def examples(ctx: Context, plots: bool = False) -> None:
"""
Run the examples.
@@ -346,7 +350,7 @@ def examples(ctx: Context, plots: bool = False):


@task(formatting, quality, precommit, tests, examples)
def preflight(ctx: Context): # noqa: ARG001
def preflight(ctx: Context) -> None: # noqa: ARG001
"""
Perform the preflight tasks, i.e., *formatting*, *tests*, *quality*, and
*examples*.
@@ -365,7 +369,7 @@ def docs(
ctx: Context,
html: bool = True,
pdf: bool = True,
):
) -> None:
"""
Build the documentation.
@@ -391,7 +395,7 @@ def docs(


@task
def todo(ctx: Context):
def todo(ctx: Context) -> None:
"""
Export the TODO items.
@@ -408,7 +412,7 @@ def todo(ctx: Context):


@task
def requirements(ctx: Context):
def requirements(ctx: Context) -> None:
"""
Export the *requirements.txt* file.
@@ -429,7 +433,7 @@ def requirements(ctx: Context):


@task(literalise, clean, preflight, docs, todo, requirements)
def build(ctx: Context):
def build(ctx: Context) -> None:
"""
Build the project and runs dependency tasks, i.e., *docs*, *todo*, and
*preflight*.
@@ -442,7 +446,8 @@ def build(ctx: Context):

message_box("Building...")
if "modified: README.rst" in ctx.run("git status").stdout: # pyright: ignore
raise RuntimeError('Please commit your changes to the "README.rst" file!')
msg = 'Please commit your changes to the "README.rst" file!'
raise RuntimeError(msg)

with open("README.rst") as readme_file:
readme_content = readme_file.read()
@@ -472,7 +477,7 @@ def build(ctx: Context):


@task
def virtualise(ctx: Context, tests: bool = True):
def virtualise(ctx: Context, tests: bool = True) -> None:
"""
Create a virtual environment for the project build.
@@ -505,7 +510,7 @@ def virtualise(ctx: Context, tests: bool = True):


@task
def tag(ctx: Context):
def tag(ctx: Context) -> None:
"""
Tag the repository according to defined version using *git-flow*.
@@ -519,7 +524,8 @@ def tag(ctx: Context):
result = ctx.run("git rev-parse --abbrev-ref HEAD", hide="both")

if result.stdout.strip() != "develop": # pyright: ignore
raise RuntimeError("Are you still on a feature or master branch?")
msg = "Are you still on a feature or master branch?"
raise RuntimeError(msg)

with open(os.path.join(PYTHON_PACKAGE_NAME, "__init__.py")) as file_handle:
file_content = file_handle.read()
@@ -539,7 +545,7 @@ def tag(ctx: Context):
1
)

version = ".".join((major_version, minor_version, change_version))
version = f"{major_version}.{minor_version}.{change_version}"

result = ctx.run("git ls-remote --tags upstream", hide="both")
remote_tags = result.stdout.strip().split("\n") # pyright: ignore
@@ -548,17 +554,18 @@ def tag(ctx: Context):
tags.add(remote_tag.split("refs/tags/")[1].replace("refs/tags/", "^{}"))
version_tags = sorted(tags)
if f"v{version}" in version_tags:
raise RuntimeError(
msg = (
f'A "{PYTHON_PACKAGE_NAME}" "v{version}" tag already exists in '
f"remote repository!"
)
raise RuntimeError(msg)

ctx.run(f"git flow release start v{version}")
ctx.run(f"git flow release finish v{version}")


@task(build)
def release(ctx: Context):
def release(ctx: Context) -> None:
"""
Release the project to *Pypi* with *Twine*.
@@ -575,7 +582,7 @@ def release(ctx: Context):


@task
def sha256(ctx: Context):
def sha256(ctx: Context) -> None:
"""
Compute the project *Pypi* package *sha256* with *OpenSSL*.
6 changes: 3 additions & 3 deletions utilities/export_todo.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
import codecs
import os

__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
@@ -34,7 +34,7 @@
-----
| **Colour** by Colour Developers
| Copyright 2013 Colour Developers - \
| Copyright 2024 Colour Developers - \
`colour-developers@colour-science.org <colour-developers@colour-science.org>`__
| This software is released under terms of BSD-3-Clause: \
https://opensource.org/licenses/BSD-3-Clause
@@ -93,7 +93,7 @@ def extract_todo_items(root_directory: str) -> dict:
return todo_items


def export_todo_items(todo_items: dict, file_path: str):
def export_todo_items(todo_items: dict, file_path: str) -> None:
"""
Export TODO items to given file.
4 changes: 2 additions & 2 deletions utilities/unicode_to_ascii.py
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
import os
import unicodedata

__copyright__ = "Copyright 2013 Colour Developers"
__copyright__ = "Copyright 2024 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
@@ -31,7 +31,7 @@
}


def unicode_to_ascii(root_directory: str):
def unicode_to_ascii(root_directory: str) -> None:
"""
Recursively convert from unicode to ASCII *.py*, *.bib* and *.rst* files
in given directory.

0 comments on commit d8bc035

Please sign in to comment.