Skip to content

Commit

Permalink
Support Python 3.13 officially (#424)
Browse files Browse the repository at this point in the history
  • Loading branch information
facelessuser authored Aug 3, 2024
1 parent 5b28181 commit 1dfdc1f
Show file tree
Hide file tree
Showing 65 changed files with 199 additions and 200 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ jobs:
max-parallel: 5
matrix:
platform: [ubuntu-latest, windows-latest]
python-version: [3.8, 3.9, '3.10', 3.11, 3.12]
python-version: [3.8, 3.9, '3.10', 3.11, 3.12, 3.13]

runs-on: ${{ matrix.platform }}

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand All @@ -82,7 +82,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Setup Node
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Setup Node
Expand Down Expand Up @@ -44,7 +44,7 @@ jobs:

steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Package
Expand Down
2 changes: 1 addition & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 - 2023 Isaac Muse
Copyright (c) 2020 - 2024 Isaac Muse

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
14 changes: 7 additions & 7 deletions coloraide/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def __new__(
raise ValueError("All version parts except 'release' should be integers.")

if release not in REL_MAP:
raise ValueError("'{}' is not a valid release type.".format(release))
raise ValueError(f"'{release}' is not a valid release type.")

# Ensure valid pre-release (we do not allow implicit pre-releases).
if ".dev-candidate" < release < "final":
Expand Down Expand Up @@ -145,15 +145,15 @@ def _get_canonical(self) -> str:

# Assemble major, minor, micro version and append `pre`, `post`, or `dev` if needed..
if self.micro == 0 and self.major != 0:
ver = "{}.{}".format(self.major, self.minor)
ver = f"{self.major}.{self.minor}"
else:
ver = "{}.{}.{}".format(self.major, self.minor, self.micro)
ver = f"{self.major}.{self.minor}.{self.micro}"
if self._is_pre():
ver += '{}{}'.format(REL_MAP[self.release], self.pre)
ver += f'{REL_MAP[self.release]}{self.pre}'
if self._is_post():
ver += ".post{}".format(self.post)
ver += f".post{self.post}"
if self._is_dev():
ver += ".dev{}".format(self.dev)
ver += f".dev{self.dev}"

return ver

Expand All @@ -164,7 +164,7 @@ def parse_version(ver: str) -> Version:
m = RE_VER.match(ver)

if m is None:
raise ValueError("'{}' is not a valid version".format(ver))
raise ValueError(f"'{ver}' is not a valid version")

# Handle major, minor, micro
major = int(m.group('major'))
Expand Down
20 changes: 10 additions & 10 deletions coloraide/algebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ def vdot(a: VectorLike, b: VectorLike) -> float:

l = len(a)
if l != len(b):
raise ValueError('Vectors of size {} and {} are not aligned'.format(l, len(b)))
raise ValueError(f'Vectors of size {l} and {len(b)} are not aligned')
s = 0.0
i = 0
while i < l:
Expand All @@ -745,7 +745,7 @@ def vcross(v1: VectorLike, v2: VectorLike) -> Any: # pragma: no cover

l1 = len(v1)
if l1 != len(v2):
raise ValueError('Incompatible dimensions of {} and {} for cross product'.format(l1, len(v2)))
raise ValueError(f'Incompatible dimensions of {l1} and {len(v2)} for cross product')

if l1 == 2:
return v1[0] * v2[1] - v1[1] * v2[0]
Expand All @@ -756,7 +756,7 @@ def vcross(v1: VectorLike, v2: VectorLike) -> Any: # pragma: no cover
v1[0] * v2[1] - v1[1] * v2[0]
]
else:
raise ValueError('Expected vectors of shape (2,) or (3,) but got ({},) ({},)'.format(l1, len(v2)))
raise ValueError(f'Expected vectors of shape (2,) or (3,) but got ({l1},) ({len(v2)},)')


@overload
Expand Down Expand Up @@ -1650,15 +1650,15 @@ def broadcast_to(a: ArrayLike | float, s: int | ShapeLike) -> Array:
ndim_orig = len(s_orig)
ndim_target = len(s)
if ndim_orig > ndim_target:
raise ValueError("Cannot broadcast {} to {}".format(s_orig, s))
raise ValueError(f"Cannot broadcast {s_orig} to {s}")

s1 = list(s_orig)
if ndim_orig < ndim_target:
s1 = ([1] * (ndim_target - ndim_orig)) + s1

for d1, d2 in zip(s1, s):
if d1 != d2 and (d1 != 1 or d1 > d2):
raise ValueError("Cannot broadcast {} to {}".format(s_orig, s))
raise ValueError(f"Cannot broadcast {s_orig} to {s}")

m = list(_BroadcastTo(a, tuple(s1), tuple(s)))
return reshape(m, s) if len(s) > 1 else m # type: ignore[return-value]
Expand Down Expand Up @@ -1965,7 +1965,7 @@ def linspace(start: ArrayLike | float, stop: ArrayLike | float, num: int = 50, e
elif s2[0] == 1:
stop = stop * s1[0] # type: ignore[operator]
else:
raise ValueError('Cannot broadcast start ({}) and stop ({})'.format(s1, s2))
raise ValueError(f'Cannot broadcast start ({s1}) and stop ({s2})')

# Apply linear interpolation steps across the vectors
values = list(zip(start, stop)) # type: ignore[arg-type]
Expand Down Expand Up @@ -2384,7 +2384,7 @@ def reshape(array: ArrayLike | float, new_shape: int | ShapeLike) -> float | Arr
if len(v) == 1:
return v[0]
# Kick out if the requested shape doesn't match the data
raise ValueError('Shape {} does not match the data total of {}'.format(new_shape, shape(array)))
raise ValueError(f'Shape {new_shape} does not match the data total of {shape(array)}')

current_shape = shape(array)

Expand All @@ -2397,7 +2397,7 @@ def reshape(array: ArrayLike | float, new_shape: int | ShapeLike) -> float | Arr
# Make sure we can actually reshape.
total = prod(new_shape) if not empty else prod(new_shape[:-1])
if not empty and total != prod(current_shape):
raise ValueError('Shape {} does not match the data total of {}'.format(new_shape, shape(array)))
raise ValueError(f'Shape {new_shape} does not match the data total of {shape(array)}')

# Create the array
m = [] # type: Any
Expand Down Expand Up @@ -2899,7 +2899,7 @@ def solve(a: MatrixLike | TensorLike, b: ArrayLike) -> Array:
x.append(_back_sub_vector(u, _forward_sub_vector(l, [vi[i] for i in p], size), size))
return reshape(x, s2) # type: ignore[return-value]

raise ValueError("Could not broadcast {} and {}".format(s, s2))
raise ValueError(f"Could not broadcast {s} and {s2}")


def trace(matrix: Matrix) -> float:
Expand Down Expand Up @@ -3137,7 +3137,7 @@ def inner(a: float | ArrayLike, b: float | ArrayLike) -> float | Array:

# If both inputs are not scalars, the last dimension must match
if (shape_a and shape_b and shape_a[-1] != shape_b[-1]):
raise ValueError('The last dimensions {} and {} do not match'.format(shape_a, shape_b))
raise ValueError(f'The last dimensions {shape_a} and {shape_b} do not match')

# If we have a scalar, we should just multiply
if (not dims_a or not dims_b):
Expand Down
2 changes: 1 addition & 1 deletion coloraide/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def __new__(
flags: int = 0,
limit: tuple[float | None, float | None] = (None, None),
nans: float = 0.0
) -> 'Channel':
) -> Channel:
"""Initialize."""

obj = super().__new__(cls, name)
Expand Down
38 changes: 19 additions & 19 deletions coloraide/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def __init__(self, color: Color, start: int, end: int) -> None:
def __str__(self) -> str: # pragma: no cover
"""String."""

return "ColorMatch(color={!r}, start={}, end={})".format(self.color, self.start, self.end)
return f"ColorMatch(color={self.color!r}, start={self.start}, end={self.end})"

__repr__ = __str__

Expand Down Expand Up @@ -245,7 +245,7 @@ def _parse(
s = color
space_class = cls.CS_MAP.get(s)
if not space_class:
raise ValueError("'{}' is not a registered color space".format(s))
raise ValueError(f"'{s}' is not a registered color space")
num_channels = len(space_class.CHANNELS)
num_data = len(data)
if num_data < num_channels:
Expand All @@ -258,7 +258,7 @@ def _parse(
else:
m = cls._match(color, fullmatch=True)
if m is None:
raise ValueError("'{}' is not a valid color".format(color))
raise ValueError(f"'{color}' is not a valid color")
coords = [alg.clamp(float(v), *c.limit) for c, v in zipl(m[0].CHANNELS, m[1])]
coords.append(alg.clamp(float(m[2]), *m[0].channels[-1].limit))
obj = m[0], coords
Expand All @@ -267,15 +267,15 @@ def _parse(
elif isinstance(color, Color):
space_class = cls.CS_MAP.get(color.space())
if not space_class:
raise ValueError("'{}' is not a registered color space".format(color.space()))
raise ValueError(f"'{color.space()}' is not a registered color space")
obj = space_class, color[:]

# Handle a color dictionary
elif isinstance(color, Mapping):
obj = cls._parse(color['space'], color['coords'], color.get('alpha', 1.0))

else:
raise TypeError("'{}' is an unrecognized type".format(type(color)))
raise TypeError(f"'{type(color)}' is an unrecognized type")

return obj

Expand Down Expand Up @@ -382,14 +382,14 @@ def register(
else:
if reset_convert_cache: # pragma: no cover
cls._get_convert_chain.cache_clear()
raise TypeError("Cannot register plugin of type '{}'".format(type(i)))
raise TypeError(f"Cannot register plugin of type '{type(i)}'")

if p.NAME != "*" and p.NAME not in mapping or overwrite:
mapping[p.NAME] = p
elif not silent:
if reset_convert_cache: # pragma: no cover
cls._get_convert_chain.cache_clear()
raise ValueError("A plugin of name '{}' already exists or is not allowed".format(p.NAME))
raise ValueError(f"A plugin of name '{p.NAME}' already exists or is not allowed")

if reset_convert_cache:
cls._get_convert_chain.cache_clear()
Expand Down Expand Up @@ -439,13 +439,13 @@ def deregister(cls, plugin: str | Sequence[str], *, silent: bool = False) -> Non
cls._get_convert_chain.cache_clear()
if not silent:
raise ValueError(
"'{}' is a reserved name gamut mapping/reduction and cannot be removed".format(name)
f"'{name}' is a reserved name gamut mapping/reduction and cannot be removed"
)
continue # pragma: no cover
else:
if reset_convert_cache: # pragma: no cover
cls._get_convert_chain.cache_clear()
raise ValueError("The plugin category of '{}' is not recognized".format(ptype))
raise ValueError(f"The plugin category of '{ptype}' is not recognized")

if name == '*':
mapping.clear()
Expand All @@ -454,7 +454,7 @@ def deregister(cls, plugin: str | Sequence[str], *, silent: bool = False) -> Non
elif not silent:
if reset_convert_cache:
cls._get_convert_chain.cache_clear()
raise ValueError("A plugin of name '{}' under category '{}' could not be found".format(name, ptype))
raise ValueError(f"A plugin of name '{name}' under category '{ptype}' could not be found")

if reset_convert_cache:
cls._get_convert_chain.cache_clear()
Expand Down Expand Up @@ -554,7 +554,7 @@ def _handle_color_input(self, color: ColorInput) -> Color:
elif self._is_color(color):
return color if self._is_this_color(color) else self.new(color)
else:
raise TypeError("Unexpected type '{}'".format(type(color)))
raise TypeError(f"Unexpected type '{type(color)}'")

def space(self) -> str:
"""The current color space."""
Expand Down Expand Up @@ -797,14 +797,14 @@ def convert_chromaticity(

# Check that we know the requested spaces
if cspace1 not in SUPPORTED_CHROMATICITY_SPACES:
raise ValueError("Unexpected chromaticity space '{}'".format(cspace1))
raise ValueError(f"Unexpected chromaticity space '{cspace1}'")
if cspace2 not in SUPPORTED_CHROMATICITY_SPACES:
raise ValueError("Unexpected chromaticity space '{}'".format(cspace2))
raise ValueError(f"Unexpected chromaticity space '{cspace2}'")

# Return if there is nothing to convert
l = len(coords)
if (cspace1 == 'xyz' and l != 3) or l not in (2, 3):
raise ValueError('Unexpected number of coordinates ({}) for {}'.format(l, cspace1))
raise ValueError(f'Unexpected number of coordinates ({l}) for {cspace1}')

# Return if already in desired form
if cspace1 == cspace2:
Expand Down Expand Up @@ -854,7 +854,7 @@ def chromatic_adaptation(

adapter = cls.CAT_MAP.get(method if method is not None else cls.CHROMATIC_ADAPTATION)
if not adapter:
raise ValueError("'{}' is not a supported CAT".format(method))
raise ValueError(f"'{method}' is not a supported CAT")

return adapter.adapt(tuple(w1), tuple(w2), xyz) # type: ignore[arg-type]

Expand Down Expand Up @@ -922,7 +922,7 @@ def fit(
mapping = self.FIT_MAP.get(method)
if not mapping:
# Unknown fit method
raise ValueError("'{}' gamut mapping is not currently supported".format(method))
raise ValueError(f"'{method}' gamut mapping is not currently supported")

mapping.fit(self, target, **kwargs)
return self
Expand Down Expand Up @@ -991,7 +991,7 @@ def mix(
interpolate_args['domain'] = interpolate.normalize_domain(domain)

if not self._is_color(color) and not isinstance(color, (str, Mapping)):
raise TypeError("Unexpected type '{}'".format(type(color)))
raise TypeError(f"Unexpected type '{type(color)}'")
mixed = self.interpolate([self, color], **interpolate_args)(percent)
return self._hotswap(mixed) if in_place else mixed

Expand Down Expand Up @@ -1185,7 +1185,7 @@ def delta_e(

delta = self.DE_MAP.get(method)
if not delta:
raise ValueError("'{}' is not currently a supported distancing algorithm.".format(method))
raise ValueError(f"'{method}' is not currently a supported distancing algorithm.")
return delta.distance(self, color, **kwargs)

def distance(self, color: ColorInput, *, space: str = "lab") -> float:
Expand Down Expand Up @@ -1288,7 +1288,7 @@ def set( # noqa: A003
# when dealing with different color spaces.
if value is None:
if isinstance(name, str):
raise ValueError("Missing the positional 'value' argument for channel '{}'".format(name))
raise ValueError(f"Missing the positional 'value' argument for channel '{name}'")

original_space = current_space = self.space()
obj = self.clone()
Expand Down
2 changes: 1 addition & 1 deletion coloraide/compositing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ def compose(
out_space = space

if not isinstance(color.CS_MAP[space], RGBish):
raise ValueError("Can only compose in an RGBish color space, not {}".format(type(color.CS_MAP[space])))
raise ValueError(f"Can only compose in an RGBish color space, not {type(color.CS_MAP[space])}")

if not backdrop:
return color
Expand Down
2 changes: 1 addition & 1 deletion coloraide/compositing/blend_modes.py
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,5 @@ def get_blender(blend: str) -> Blend:

blender = SUPPORTED.get(blend)
if not blender:
raise ValueError("'{}' is not a recognized blend mode".format(blend))
raise ValueError(f"'{blend}' is not a recognized blend mode")
return blender
2 changes: 1 addition & 1 deletion coloraide/compositing/porter_duff.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,5 +239,5 @@ def compositor(name: str) -> type[PorterDuff]:

composite = SUPPORTED.get(name)
if not composite:
raise ValueError("'{}' compositing is not supported".format(name))
raise ValueError(f"'{name}' compositing is not supported")
return composite
2 changes: 1 addition & 1 deletion coloraide/contrast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ def contrast(name: str | None, color1: Color, color2: Color, **kwargs: Any) -> f

method = color1.CONTRAST_MAP.get(name)
if not method:
raise ValueError("'{}' contrast method is not supported".format(name))
raise ValueError(f"'{name}' contrast method is not supported")

return method.contrast(color1, color2, **kwargs)
2 changes: 1 addition & 1 deletion coloraide/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def calc_path_to_xyz(

obj = color.CS_MAP.get(space)
if obj is None:
raise ValueError("'{}' is not a valid color space".format(space))
raise ValueError(f"'{space}' is not a valid color space")

# Create a worse case conversion chain from XYZ to the target
temp = obj
Expand Down
Loading

0 comments on commit 1dfdc1f

Please sign in to comment.