././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1670223976.8445575 webcolors-25.10.0/.editorconfig0000644000000000000000000000042114343314151013255 0ustar00# https://editorconfig.org/ root = true [*] indent_style = space indent_size = 4 insert_final_newline = true trim_trailing_whitespace = true end_of_line = lf charset = utf-8 [*.py] max_line_length = 88 [docs/**.rst] max_line_length = 79 [*.{yaml,yml}] indent_size = 2 ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761802994.2171693 webcolors-25.10.0/.flake80000644000000000000000000000024715100575362011766 0ustar00[flake8] extend-ignore = E203 extend-select = B950 max-complexity = 13 max-line-length = 88 pytest-parametrize-names-type = list pytest-parametrize-values-type = list ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761526744.3559592 webcolors-25.10.0/.pre-commit-config.yaml0000644000000000000000000000201715077541730015076 0ustar00# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: check-added-large-files - id: check-ast - id: check-case-conflict - id: check-docstring-first - id: check-merge-conflict - id: check-toml - id: check-yaml - id: debug-statements - id: end-of-file-fixer - id: fix-byte-order-marker - id: trailing-whitespace - repo: https://github.com/psf/black rev: 25.9.0 hooks: - id: black language_version: python3.14 name: black (Python formatter) - repo: https://github.com/pycqa/flake8 rev: 7.3.0 hooks: - id: flake8 name: flake8 (Python linter) - repo: https://github.com/econchick/interrogate rev: 1.7.0 hooks: - id: interrogate name: interrogate (Python docstring enforcer) args: [--quiet] - repo: https://github.com/pycqa/isort rev: 7.0.0 hooks: - id: isort name: isort (Python formatter) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731289898.1409817 webcolors-25.10.0/.readthedocs.yaml0000644000000000000000000000051314714261452014040 0ustar00# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-22.04 tools: python: "3.12" sphinx: configuration: docs/conf.py python: install: - method: pip path: . - requirements: docs/requirements.txt ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731289353.0168886 webcolors-25.10.0/CONTRIBUTING.rst0000644000000000000000000000660714714260411013256 0ustar00Contributor guide ================= Contributions are welcome, and several things about this repository have been set up to make the process easier for everyone (including you!). Prerequisites ------------- * Please use a code editor/IDE that supports `EditorConfig `_. Most editors do nowadays, so you probably don't have to worry about it, but it will help to automatically apply some formatting and style rules. * Please make sure you have `pre-commit `_ installed, and in your local checkout of this repository run ``pre-commit install`` to set up the pre-commit hooks. The following two tools are *required* for working with this repository: * `PDM `_ * `nox `_ You will also need at least one supported Python version. It is also recommended that you test against *all* the supported Python verisions before opening a pull request; you can use `PDM's Python installer `_ to install any versions of Python you need. Local setup ----------- Once you have the tools above installed, run the following in the root of your git checkout:: pdm install This will create a local virtual environment and install ``webcolors`` and its dependencies. Testing ------- To run the tests, use ``nox``:: nox --tags tests By default this will run against as many supported Python versions as you have installed. To select a single specific Python version, you can run:: nox --tags tests --python "3.11" You can also run the full CI suite locally by just invoking ``nox``. This will run the tests, check the documentation, lint the code and check formatting, and build a package and perform checks on it. For more information about available tasks, run ``nox --list`` or read the file ``noxfile.py`` in the root of your source checkout, or the testing documentation in the file ``docs/conformance.rst``. Code style ---------- The pre-commit hooks will auto-format code with `isort `_ and `Black `_. Many editors and IDEs also support auto-formatting with these tools every time you save a file. The CI suite will disallow any code that does not follow the isort/Black format. All code must also be compatible with all supported versions of Python. Other guidelines ---------------- * If you need to add a new file of code, please make sure to put a license identifier comment near the top of the file. You can copy and paste the license identifier comment from any existing file, where it looks like this: ``# SPDX-License-Identifier: BSD-3-Clause`` * Documentation and tests are not just recommended -- they're required. Any new file, class, method or function must have a docstring and must either include that docstring (via autodoc) in the built documentation, or must have manually-written documentation in the ``docs/`` directory. Any new feature or bugfix must have sufficient tests to prove that it works, and the test coverage report must come out at 100%. The CI suite will fail if test coverage is below 100%, if there's any code which doesn't have a docstring, or if there are any misspelled words in the documentation (and if there's a word the spell-checker should learn to recognize, add it to ``docs/spelling_wordlist.txt``). ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716183956.9569664 webcolors-25.10.0/LICENSE0000644000000000000000000000277314622561625011633 0ustar00Copyright (c) James Bennett, and contributors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the author nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1760945115.0967665 webcolors-25.10.0/Makefile0000644000000000000000000000477615075361733012274 0ustar00# This Makefile is not used to compile particular files or sets of # files, as in a C/C++ project, but instead is used as a task runner # and centralized point for defining local development tasks. # # This Makefile also is an experimental feature for this repository, # and may be removed without warning. # # You can view a list of available tasks by running "make help" or # just "make". # .POSIX: .PHONY: help help: ## Display help for this Makefile. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) .DEFAULT: help # Targets for managing the local development environment. # ------------------------------------------------------------------------------ .PHONY: clean clean: ### Clean the local directory of temporary files/artifacts. @rm -rf dist/ @rm -rf .coverage* @find . -name '*.egg-info' -exec rm -rf {} + .PHONY: destroy destroy: ### Remove the PDM local virtual environment. @rm -rf .venv .PHONY: install install: ### Set up the local development environment, including pre-commit hooks and local venv. @pre-commit install @pre-commit install-hooks @pdm install .PHONY: lock lock: ### Update PDM dependency lockfiles. @pdm lock .PHONY: update update: ### Update dependencies and tooling. @pre-commit autoupdate pdm update # Targets for running tests and checks locally. # ------------------------------------------------------------------------------ .PHONY: check-docs check-docs: ### Check the package's documentation. @nox -k docs .PHONY: check-format check-format: ### Check code formatting. @nox -k formatters .PHONY: check-package check-package: ### Check the package build and contents. @nox -k packaging .PHONY: ci ci: ### Run entire CI test/check suite locally: tests with coverage, docs, linters, format checks, package checks. @nox -k "not release" .PHONY: format format: ### Apply autoformatters to the entire codebase. @pre-commit run --all-files black @pre-commit run --all-files isort .PHONY: lint lint: ### Run linter suite over the codebase. @nox -k linters .PHONY: pre-commit pre-commit: ### Run all pre-commit hooks. @pre-commit run --all-files .PHONY: test test: ### Run unit tests with coverage report. @nox -k "tests and not release" # Targets for packaging. # ------------------------------------------------------------------------------ .PHONY: package package: ### Build the package. @pdm build ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716880291.6507416 webcolors-25.10.0/README.rst0000644000000000000000000000225214625301644012301 0ustar00.. -*-restructuredtext-*- .. image:: https://github.com/ubernostrum/webcolors/workflows/CI/badge.svg :alt: CI status image :target: https://github.com/ubernostrum/webcolors/actions?query=workflow%3ACI ``webcolors`` is a module for working with and converting between the various HTML/CSS color formats. Support is included for normalizing and converting between the following formats (RGB colorspace only; conversion to/from HSL can be handled by the ``colorsys`` module in the Python standard library): * Specification-defined color names * Six-digit hexadecimal * Three-digit hexadecimal * Integer ``rgb()`` triplet * Percentage ``rgb()`` triplet For example: .. code-block:: python >>> import webcolors >>> webcolors.hex_to_name("#daa520") 'goldenrod' Implementations are also provided for the HTML5 color parsing and serialization algorithms. For example, parsing the infamous "chucknorris" string into an ``rgb()`` triplet: .. code-block:: python >>> import webcolors >>> webcolors.html5_parse_legacy_color("chucknorris") HTML5SimpleColor(red=192, green=0, blue=0) Full documentation is `available online `_. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1378901941.0 webcolors-25.10.0/docs/Makefile0000644000000000000000000000566712214057665013222 0ustar00# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf _build/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) _build/html @echo @echo "Build finished. The HTML pages are in _build/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml @echo @echo "Build finished. The HTML pages are in _build/dirhtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) _build/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in _build/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) _build/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in _build/qthelp, like this:" @echo "# qcollectiongenerator _build/qthelp/webcolors.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile _build/qthelp/webcolors.qhc" latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex @echo @echo "Build finished; the LaTeX files are in _build/latex." @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ "run these through (pdf)latex." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) _build/changes @echo @echo "The overview file is in _build/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) _build/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in _build/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) _build/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in _build/doctest/output.txt." ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761896772.1670084 webcolors-25.10.0/docs/changelog.rst0000644000000000000000000002104415101064504014212 0ustar00.. _changelog: Changelog ========= This document lists the changes in each release of ``webcolors``. Version numbering ----------------- This library currently tracks its version numbers using the ``YY.MM.MICRO`` form of `Calendar Versioning `_ ("CalVer"), in which the first two components of the version number are the (two-digit) year and (non-zero-padded) month of the release date, while the third component is an incrementing value for releases occurring in that month. For example, the first release issued in January 2025 would have a version number of 25.1.0; a subsequent release in the same month would be 25.1.1; a release the following month (February) would be 25.2.0. The CalVer system was adopted for this library in 2024, and the first release to use a CalVer version number was 24.6.0. API stability and deprecations ------------------------------ The API stability/deprecation policy for this library is as follows: * The supported stable public API of this library is the set of symbols which are exported by its ``__all__`` declaration and which are documented in this documentation. For classes exported there, the supported stable public API is the set of methods and attributes of those classes whose names do not begin with one or more underscore (``_``) characters and which are documented in this documentation. * When a public API is to be removed, or undergo a backwards-incompatible change, it will emit a deprecation warning which serves as notice of the intended removal or change, and which will give a date -- which will always be at least in the next calendar year after the first release which emits the deprecation warning -- past which the removal or change may occur without further warning. * Security fixes, and fixes for high-severity bugs (such as those which might cause unrecoverable crash or data loss), are not required to emit deprecation warnings, and may -- if needed -- impose backwards-incompatible change in any release. If this occurs, this changelog document will contain a note explaining why the usual deprecation process could not be followed for that case. * This policy is in effect as of the adoption of CalVer versioning, with version 24.6.0 of this library. Releases under CalVer --------------------- Version 25.10.0 ~~~~~~~~~~~~~~~ Released October 2025 * Supported Python versions are now 3.10, 3.11, 3.12, 3.13, and 3.14. * The package's tests now run with `pytest `_. Version 24.11.1 ~~~~~~~~~~~~~~~ Released November 2024 * Correct an error with specifying the version in the 24.11.0 package. The version number of the package was accidentally defined in two places, and they were out of sync. The extra definition has been removed. The 24.11.0 package has been yanked from PyPI to prevent problems. Version 24.11.0 ~~~~~~~~~~~~~~~ Released November 2024 * Supported Python versions are now 3.9, 3.10, 3.11, 3.12, and 3.13. Version 24.8.0 ~~~~~~~~~~~~~~ Released August 2024 * Added the :func:`~webcolors.names` function to allow retrieving lists of color names. The underlying mappings of color names/values still are not supported API; to obtain the color value corresponding to a name, use the appropriate conversion function. Version 24.6.0 ~~~~~~~~~~~~~~ Released June 2024 * Supported Python versions are now 3.8, 3.9, 3.10, and 3.12. * Running the unit tests no longer uses a third-party test runner; the standard-library ``unittest`` module's runner is used instead. * Documentation of the HTML5 color algorithms has been updated to emphasize which HTML5 spec is used (the WHATWG spec, which is now the only canonical and maintained HTML5 spec) and comments in the implementations have been updated to include the latest prose description of the HTML5 algorithms from the spec. These updates do not change the behavior of the HTML5 algorithms, and are only for clarity of documentation and explanation. * Adopted `CalVer versioning `_. * The raw mappings of color names/values are no longer publicly exposed; use the appropriate normalizing conversion functions instead of accessing the mappings directly. Releases not under CalVer ------------------------- Version 1.13 ~~~~~~~~~~~~ Released March 2023 * Supported Python versions are now 3.7, 3.8, 3.9, 3.10, and 3.11. * The codebase was significantly reorganized and modernized. Public API is unchanged. Imports should continue to be directly from the top-level ``webcolors`` module; attempting to import from submodules is not supported. * Now packaging declaratively via ``pyproject.toml`` with `PEP 517 `_ support from ``setuptools``. Version 1.12 ~~~~~~~~~~~~ Released May 2022 * Supported Python versions are now 3.7, 3.8, 3.9, and 3.10. Version 1.11.1 ~~~~~~~~~~~~~~ Released February 2020 * Corrected an error regarding supported Python versions in the README file. Version 1.11 ~~~~~~~~~~~~ Released February 2020 * Python 2 has reached the end of its support cycle from the Python core team; accordingly, Python 2 support is dropped. Supported Python versions are now 3.5, 3.6, 3.7, and 3.8. Version 1.10 ~~~~~~~~~~~~ Released September 2019 * Similar to the change in version 1.9 which normalized conversions to named colors for ``gray``/``grey`` to always use the ``gray`` variant, the other named grays of CSS3 now normalize to the ``gray`` spelling. This affects the following colors: ``darkgray``/``darkgrey``, ``darkslategray``/``darkslategrey``, ``dimgray``/``dimgrey``, ``lightgray``/``lightgrey``, ``lightslategray``/``lightslategrey``, ``slategray``/``slategrey``. Version 1.9.1 ~~~~~~~~~~~~~ Released June 2019 * The ``__version__`` attribute of the installed ``webcolors`` module, although not documented or referenced anywhere, was accidentally not updated in the 1.9 release. It has now been updated (and now indicates 1.9.1). Version 1.9 ~~~~~~~~~~~ Released June 2019 * Added :ref:`a set of constants to use when referring to specifications that define color names `. * When asked to provide a color name, using the CSS3/SVG set of names, for the hexadecimal value ``#808080``, the integer triplet ``rgb(128, 128, 128)``, or the percentage triplet ``rgb(50%, 50%, 50%)``, ``webcolors`` now always returns ``u'gray'``, never ``u'grey'``. Previously, the behavior could be inconsistent as it depended on the Python version in use; ``u'gray'`` was picked because it was the spelling variant used in HTML 4, CSS1, and CSS2. Version 1.8.1 ~~~~~~~~~~~~~ Released February 2018 * The 1.8.1 release is a repackaging of 1.8 to produce both source (.tar.gz) and binary (.whl) package formats, following reports that the source-package-only release of 1.8 was causing installation issues for some users. See `issue 6 in the repository `_ for details. Version 1.8 ~~~~~~~~~~~ Released February 2018 * Added the :class:`~webcolors.IntegerRGB`, :class:`~webcolors.PercentRGB`, and :class:`~webcolors.HTML5SimpleColor` named tuples. * Drop support for Python 3.3 (Python core team no longer maintains 3.3). * Mark support for Python 3.6. * :ref:`The full verification tests ` now run correctly on Python 3. Version 1.7 ~~~~~~~~~~~ Released November 2016 * Drop support for Python 2.6 (Python core team no longer maintains 2.6). * Mark support for Python 3.4. * On Python 3, the use of :class:`str` for all functions which take string arguments is now mandatory. Attempted use of :class:`bytes` will raise an exception. On Python 2, use of bytestrings is still permitted. Version 1.5.1 ~~~~~~~~~~~~~ Released November 2015 * Corrected multiple typos in documentation. Version 1.5 ~~~~~~~~~~~ Released March 2015 * Python 3 support: ``webcolors`` now supports Python 3.3. * Added :ref:`HTML5 color algorithms `. Version 1.4 ~~~~~~~~~~~ Released February 2012 * Integer and percentage ``rgb()`` triplets now normalized in accordance with CSS clipping rules. * Preparatory work for Python 3 support. Version 1.3.1 ~~~~~~~~~~~~~ Released October 2009 * Documentation expanded. * Documentation now maintained using `Sphinx `_. Version 1.3 ~~~~~~~~~~~ * Documentation expanded. Version 1.2 ~~~~~~~~~~~ Released March 2009 * Corrected the download URL in the ``setup.py`` script. Version 1.1 ~~~~~~~~~~~ Released December 2008 * Documentation expanded. Version 1.0 ~~~~~~~~~~~ Released October 2008 * Initial stable release of ``webcolors``. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716874669.3025258 webcolors-25.10.0/docs/colors.rst0000644000000000000000000001630714625266655013617 0ustar00.. _colors: An overview of colors on the web ================================ Colors on the web are typically specified in `the sRGB color space`_, where each color is made up of a red component, a green component and a blue component. This maps to the red, green and blue components of the pixels on a computer display, and to the three sets of cone cells in an average trichromatic human eye, whose peak responses are (roughly) to the wavelengths of light associated with red, green and blue. On the web, sRGB colors are specified in formats which describe the color as a 24-bit integer, where the first 8 bits provide the red value, the second 8 bits the green value and the final 8 bits the blue value. This gives a total space of 256 × 256 × 256 or 16,777,216 unique colors, though due to differences in display technology not all of these colors may be clearly distinguishable on any given physical display. HTML 4 ------ HTML 4 defined `two ways to specify sRGB colors`_: * The character ``#`` followed by three pairs of hexadecimal digits, specifying values for red, green and blue components in that order; for example, ``#0099cc``. * A set of predefined color names which correspond to specific hexadecimal values; for example, ``blue``. HTML 4 defines sixteen such colors. CSS1 ---- In `its description of color units`_, CSS1 added three new ways to specify sRGB colors: * The character ``#`` followed by three hexadecimal digits, which is expanded into three hexadecimal pairs by repeating each digit; thus ``#09c`` is equivalent to ``#0099cc``. * The string ``rgb``, followed by parentheses, between which are three base-10 integers in the range 0..255, which are taken to be the values of the red, green and blue components in that order; for example, ``rgb(0, 153, 204)``. * The same as above, except using percentages instead of numeric values; for example, ``rgb(0%, 60%, 80%)``. CSS1 also suggested a set of sixteen color names. These names were identical to the set defined in HTML 4, but CSS1 did not provide definitions of their values and stated that they were taken from "the Windows VGA palette". CSS2 ---- In its `section on colors`_, CSS2 allowed the same methods of specifying colors as CSS1, and defined and provided values for sixteen named colors, identical to the set found in HTML 4. CSS2 also specified `a list of names of system colors`_. These had no fixed color values, but would take on values from the operating system or other user interface, and allowed elements to be styled using the same colors as the surrounding user interface. These names are deprecated as of CSS3. The CSS2.1 revision did not add any new methods of specifying sRGB colors, but did define `one additional named color`_: ``orange``. CSS3 ---- `The CSS3 color module`_ adds one new way to specify colors: * A hue-saturation-lightness triplet (HSL), using the construct ``hsl()``. CSS3 also adds support for variable opacity of colors, by allowing the specification of alpha-channel information through the ``rgba()`` and ``hsla()`` constructs. These are used similarly to the ``rgb()`` and ``hsl()`` constructs, except a fourth value is supplied indicating the level of opacity from ``0.0`` (completely transparent) to ``1.0`` (completely opaque). Though not technically a color, the keyword ``transparent`` is also made available in lieu of a color value, and corresponds to ``rgba(0,0,0,0)``. CSS3 also defines a new set of 147 color names. This set is taken directly from `the named colors defined for SVG (Scalable Vector Graphics)`_ markup, and is a superset of the named colors defined in CSS2.1. HTML5 ----- HTML5 originally existed in two forms: a living document maintained by WHATWG, and a W3C Recommendation. The W3C Recommendation has now been "retired" in favor of the WHATWG living standard. HTML5 does not introduce any new methods of specifying colors, but does simplify the description of colors and introduce useful terminology. * A set of three 8-bit numbers representing the red, blue and green components of an sRGB color is termed a "simple color". * A seven-character string which begins with the character ``#``, followed by six ASCII hex digits (i.e., ``A-Fa-f0-9``), representing the red, green and blue components of an sRGB color, is a "valid simple color". * A valid simple color expressed with only lowercase ASCII hex digits (i.e., ``a-f0-9``) is a "valid lowercase simple color". HTML5 provides three algorithms related to colors: 1. An algorithm for parsing simple color values, which works on any string that is a valid simple color as defined above. 2. An algorithm for serializing simple color values, which will always produce a valid lowercase simple color. 3. A legacy color-parsing algorithm, which will yield a simple color from a variety of inputs, including inputs which are valid simple colors, inputs which are valid for formats from other standards, and certain types of "junk" inputs which were common in real-world documents. The HTML5 legacy parsing algorithm does not support the non-color keyword ``transparent`` from CSS3 and will produce an error for that input. It also does not recognize the CSS2 "system color" keywords; it will convert each such keyword to a simple color, consistently, but in a way which does not follow CSS2's definitions of these keywords (which itself was system- and configuration-dependent). The implementations in this module are based on the definitions and algorithms of `the HTML5 specification's section on colors`_. .. _the sRGB color space: http://www.w3.org/Graphics/Color/sRGB .. _two ways to specify sRGB colors: http://www.w3.org/TR/html401/types.html#h-6.5 .. _its description of color units: http://www.w3.org/TR/CSS1/#color-units .. _section on colors: http://www.w3.org/TR/CSS2/syndata.html#color-units .. _a list of names of system colors: http://www.w3.org/TR/CSS2/ui.html#system-colors .. _one additional named color: https://www.w3.org/TR/CSS21/changes.html#q21.2 .. _The CSS3 color module: http://www.w3.org/TR/css3-color/ .. _the named colors defined for SVG (Scalable Vector Graphics): http://www.w3.org/TR/SVG11/types.html#ColorKeywords .. _the HTML5 specification's section on colors: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#colours .. _support: What this module supports ------------------------- The ``webcolors`` module supports the following methods of specifying sRGB colors, and conversions between them: * Six-digit hexadecimal * Three-digit hexadecimal * Integer ``rgb()`` triplet * Percentage ``rgb()`` triplet * The defined named colors of HTML 4, CSS2, CSS2.1, and CSS3 The ``webcolors`` module **does not support**: * The CSS1 named colors, which did not have defined values * The CSS2 system colors, which did not have fixed values * The ``transparent`` keyword, which denotes an effective lack of color * Opacity/alpha-channel information specified via the ``rgba()`` construct * Colors specified in the HSL color space, via ``hsl()`` or `hsla()` constructs If you need to convert between sRGB-specified colors and HSL-specified colors, or colors specified via other means, consult the :mod:`colorsys` module in the Python standard library, which can perform conversions amongst several common color systems. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1731291571.1258194 webcolors-25.10.0/docs/conf.py0000644000000000000000000000341714714264663013055 0ustar00""" Configuration file for the Sphinx documentation builder: https://www.sphinx-doc.org/ """ import doctest import sys from importlib.metadata import version as get_version extensions = [ "notfound.extension", "sphinx.ext.autodoc", "sphinx.ext.doctest", "sphinx.ext.intersphinx", "sphinx.ext.viewcode", "sphinxext.opengraph", "sphinx_copybutton", "sphinx_inline_tabs", ] templates_path = ["_templates"] source_suffix = {".rst": "restructuredtext"} master_doc = "index" project = "webcolors" copyright = "James Bennett and contributors" version = get_version("webcolors") release = version exclude_trees = ["_build"] pygments_style = "sphinx" htmlhelp_basename = "webcolorsdoc" latex_documents = [ ("index", "webcolors.tex", "webcolors Documentation", "James Bennett", "manual"), ] html_theme = "furo" intersphinx_mapping = { "python": ("https://docs.python.org/3", None), } # Spelling check needs an additional module that is not installed by default. Add it # only if spelling check is requested so docs can be generated without it. if "spelling" in sys.argv: extensions.append("sphinxcontrib.spelling") # Spelling language. spelling_lang = "en_US" # Location of word list. spelling_word_list_filename = "spelling_wordlist.txt" # The documentation does not include contributor names, so we skip this because it's # flaky about needing to scan commit history. spelling_ignore_contributor_names = False # Doctest configuration. doctest_global_setup = "from webcolors import *" doctest_default_flags = ( doctest.DONT_ACCEPT_TRUE_FOR_1 | doctest.ELLIPSIS | doctest.IGNORE_EXCEPTION_DETAIL | doctest.NORMALIZE_WHITESPACE ) # OGP metadata configuration. ogp_enable_meta_description = True ogp_site_url = "https://webcolors.readthedocs.io/" ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761894635.2722766 webcolors-25.10.0/docs/conformance.rst0000644000000000000000000001713015101060353014554 0ustar00.. _conformance: Conformance and testing ======================= Much of the behavior of ``webcolors`` is dictated by the relevant web standards, which define the acceptable color formats, how to determine valid values for each format and the values corresponding to defined color names. Maintaining correct conversions and conformance to those standards is crucial. The source distribution of ``webcolors`` (the ``.tar.gz`` file you can download from the Python Package Index) includes a ``tests/`` directory containing a normal test suite as well as supplemental test files which perform more comprehensive verification. The normal test suite --------------------- The normal test suite for ``webcolors`` aims for 100% coverage of code paths, but does *not* aim for 100% coverage of possible color value inputs and outputs. Instead, it uses a small number of test values to routinely exercise various functions. The test values used in most test functions are chosen to provide, where applicable, at least one of each of the following types of values: * An endpoint of the acceptable range of values (i.e., ``#ffffff`` and/or ``#000000`` for hexadecimal). * A value beyond the high end of the acceptable range (i.e., greater than 255 in an integer triplet, or greater than 100% for a percentage triplet). * A value beyond the low end of the acceptable range (i.e., less than 0 in an integer triplet, or less than 0% for a percentage triplet). * A "negative zero" value (-0 in an integer triplet, or -0% in a percentage triplet). * An arbitrary value not from an endpoint of the acceptable range (usually ``#000080``, chosen because the author likes navy blue). * A value which corresponds to a named color in CSS3/SVG but not in earlier standards (usually ``#daa520``, which is ``goldenrod`` in CSS3/SVG). Since this covers the cases most likely to produce problems, this test suite provides good basic confidence in the correctness of the tested functions. It runs on every commit to the repository, and on every release tag. You can see the results of test runs online `at GitHub `_. However, the normal test suite cannot guarantee that the color definitions included in ``webcolors`` correspond to those in the relevant standards, and cannot provide guarantees of correct conversions for all possible values. For that, additional tests are required. .. _full-verification: Full verification tests ----------------------- These tests are contained in two files which are not executed during normal test runs: ``tests/definitions.py`` and ``tests/full_colors.py``. They are not run as part of the normal test suite, but are run prior to each release of ``webcolors``. Verifying color definitions ~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``definitions.py`` test file verifies that the color definitions in ``webcolors`` are correct. It does this by downloading the relevant standards documents as HTML, parsing out the color definitions in them, and comparing them to the definitions in ``webcolors``. That consists of: * Parsing out the names and hexadecimal values of the 16 named colors in the HTML 4 standard, and checking that the names and values in :data:`~webcolors.HTML4_NAMES_TO_HEX` match. * Parsing out the names and hexadecimal values of the 17 named colors in the CSS2.1 standard, and checking that the names and values in :data:`~webcolors.CSS21_NAMES_TO_HEX` match. * Parsing out the names and hexadecimal and integer values of the 147 named colors in the CSS3 color module (although the color set is taken from SVG, CSS3 provides both hexadecimal and integer values for them, while the SVG standard provides only integer values), and checking that the names and values in :data:`~webcolors.CSS3_NAMES_TO_HEX` match, and that :func:`~webcolors.name_to_rgb` returns the correct integer values. Fully verifying correctness of conversions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``full_colors.py`` test file exercises :func:`~webcolors.hex_to_rgb`, :func:`~webcolors.rgb_to_hex`, :func:`~webcolors.rgb_to_rgb_percent` and :func:`~webcolors.rgb_percent_to_rgb` as fully as is practical. For conversions between hexadecimal and integer ``rgb()``, it generates all 16,777,216 possible color values for each format in order (starting at ``#000000`` and ``(0,0,0)`` and incrementing), and verifies that each one converts to the corresponding value in the other format. Thus, it is possible to be confident that ``webcolors`` provides correct conversions between all possible color values in those formats. Testing the correctness of conversion to and from percentage ``rgb()``, however, is more difficult, and a full test is not provided, for two reasons: 1. Because percentage ``rgb()`` values can make use of floating-point values, and because standard floating-point types in most common programming languages (Python included) are inherently imprecise, exact verification is not possible. 2. The only rigorous definition of the format of a percentage value is in CSS2, `which declares a percentage to be `_ "a ```` immediately followed by '%'". `The CSS2 definition of a number `_ places no limit on the length past the decimal point, and appears to be declaring any real number as a valid value, though percentage triplets clip their inputs to the range 0.0-100.0. As the subset of reals in the range 0.0 to 100.0 is uncountably infinite, testing all legal values is not possible on current hardware in any reasonable amount of time. Since precise correctness and completeness are not achievable, ``webcolors`` instead aims to achieve *consistency* in conversions. Specifically, the ``full_colors.py`` test generates all 16,777,216 integer ``rgb()`` triplets, and for each such triplet ``t`` verifies that the following assertion holds: .. code-block:: python t == rgb_percent_to_rgb(rgb_to_rgb_percent(t)) Running the tests ----------------- The standard test runner for ``webcolors`` is `nox `_, which supports testing against multiple Python versions and executing a variety of different test tasks. The source distribution of ``webcolors`` includes its ``noxfile.py`` file. To run the tests, install nox (``pip install nox``), then download and unpack a git checkout or source package of ``webcolors``. To run the normal test suite: .. tab:: macOS/Linux/other Unix .. code-block:: shell python -m pip install nox python -m nox .. tab:: Windows .. code-block:: shell py -m pip install nox py -m nox This will attempt to test against each supported version of Python (for ``webcolors`` |release|, this is 3.10, 3.11, 3.12, 3.13, and 3.14), skipping any which are missing on your machine. To test only against a specific version of Python, use the ``--python`` flag and pass the version to test. For example, to test only on Python 3.10: .. tab:: macOS/Linux/other Unix .. code-block:: shell python -m nox -t tests --python "3.10" .. tab:: Windows .. code-block:: shell py -m nox -t tests --python "3.10" To run the full verification tests for definition correctness and conversions, specify the "release" test keyword instead (so named because these tests are usually run only prior to a new release of ``webcolors``): .. tab:: macOS/Linux/other Unix .. code-block:: shell python -m nox --keyword release .. tab:: Windows .. code-block:: shell py -m nox --keyword release Note that this requires an internet connection, and is CPU-intensive. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723014956.3971565 webcolors-25.10.0/docs/contents.rst0000644000000000000000000001104514654617454014144 0ustar00.. module:: webcolors .. _contents: API reference ============= The contents of the ``webcolors`` module fall into five categories: 1. A set of (optional) data types for representing color values. 2. Constants for several purposes. 3. Normalization functions which sanitize input in various formats prior to conversion or output. 4. Conversion functions between each method of specifying colors. 5. Implementations of the color parsing and serialization algorithms in HTML5. See :ref:`the documentation regarding conventions ` for information regarding the types and representation of various color formats in ``webcolors``. All conversion functions which involve color names take an optional argument to determine the specification from which to draw color names. See :ref:`the set of specification identifiers ` for valid values. All conversion functions, when faced with identifiably invalid hexadecimal color values, or with a request to name a color which has no name in the requested specification, or with an invalid specification identifier, will raise :exc:`ValueError`. .. admonition:: **Imports and submodules** The public, supported API of ``webcolors`` is exported from its top-level module, ``webcolors``. Although the codebase is internally organized into several submodules for easier maintenance, the existence, names, and organization of these submodules is *not* part of ``webcolors``' supported API and cannot be relied upon. For example: although :func:`normalize_hex` is actually implemented in a submodule named ``webcolors._normalization``, it must always be referred to as ``webcolors.normalize_hex``, **never** as ``webcolors._normalization.normalize_hex``. Data types ---------- Integer and percentage ``rgb()`` triplets, and HTML5 simple colors, can be passed to functions in ``webcolors`` as plain 3-:class:`tuple` of the appropriate data type. But the following :class:`~typing.NamedTuple` instances are also provided to represent these types more richly, and functions in ``webcolors`` which return triplets or simple colors will return instances of these: .. autoclass:: IntegerRGB .. autoclass:: PercentRGB .. autoclass:: HTML5SimpleColor Additionally, to aid in type annotations, the following type aliases are defined, and used throughout this module: .. autodata:: IntTuple .. autodata:: PercentTuple .. _spec-constants: Constants --------- The following constants are available for indicating the specification from which to draw color name choices, in functions which can work with multiple specifications. .. data:: CSS2 Represents the CSS2 specification. Value is ``"css2"``. .. data:: CSS21 Represents the CSS2.1 specification. Value is ``"css21"``. .. data:: CSS3 Represents the CSS3 specification. Value is ``"css3"``. .. data:: HTML4 Represents the HTML 4 specification. Value is ``"html4"``. Informative functions --------------------- .. autofunction:: names Normalization functions ----------------------- .. autofunction:: normalize_hex .. autofunction:: normalize_integer_triplet .. autofunction:: normalize_percent_triplet Conversions from color names to other formats --------------------------------------------- .. autofunction:: name_to_hex .. autofunction:: name_to_rgb .. autofunction:: name_to_rgb_percent Conversions from hexadecimal color values to other formats ---------------------------------------------------------- .. autofunction:: hex_to_name .. autofunction:: hex_to_rgb .. autofunction:: hex_to_rgb_percent Conversions from integer ``rgb()`` triplets to other formats ------------------------------------------------------------ .. autofunction:: rgb_to_name .. autofunction:: rgb_to_hex .. autofunction:: rgb_to_rgb_percent Conversions from percentage ``rgb()`` triplets to other formats --------------------------------------------------------------- .. autofunction:: rgb_percent_to_name .. autofunction:: rgb_percent_to_hex .. autofunction:: rgb_percent_to_rgb .. _html5-algorithms: HTML5 color algorithms ---------------------- .. warning:: Previously there were two competing HTML5 standards: one from WHATWG, and one from W3C. The WHATWG version is now the sole official HTML5 standard, and so the functions documented below implement the HTML5 color algorithms as given in `section 2.3.6 of the WHATWG HTML5 standard `_. .. autofunction:: html5_parse_simple_color .. autofunction:: html5_serialize_simple_color .. autofunction:: html5_parse_legacy_color ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1716272009.202338 webcolors-25.10.0/docs/conventions.rst0000644000000000000000000001246314623035611014642 0ustar00.. _conventions: Normalization and conventions ============================= Since the various formats used to specify colors in web documents do not always map cleanly to Python data types, and some variation is permitted in how to use each format in a web document, ``webcolors`` applies a set of conventions for representing color names and values, and for normalizing them. .. _string-types: Python string types ------------------- As Python 2 is no longer supported by the Python core team, ``webcolors`` now supports only Python 3, where the string type is a Unicode string. Python 3 does still have the :class:`bytes` type, but all string arguments to functions in ``webcolors`` must be :class:`str` and never :class:`bytes`. Hexadecimal color values ------------------------ For colors specified via hexadecimal values, ``webcolors`` will accept strings in the following formats: * The character ``#`` followed by three hexadecimal digits, where digits A-F may be upper- or lowercase. * The character ``#`` followed by six hexadecimal digits, where digits A-F may be upper- or lowercase (i.e., what HTML5 designates a "valid simple color" when all digits are uppercase, and a "valid lowercase simple color" when all digits are lowercase). For output which consists of a color specified via hexadecimal values, and for functions which perform intermediate conversion to hexadecimal before returning a result in another format, ``webcolors`` always normalizes such values to a string in the following format: * The character ``#`` followed by six hexadecimal digits, with digits A-F forced to lowercase (what HTML5 designates a "valid lowercase simple color"). The function :func:`~webcolors.normalize_hex` can be used to perform this normalization manually if desired. Integer and percentage ``rgb()`` triplets ----------------------------------------- Functions which work with integer ``rgb()`` triplets accept them as a 3-:class:`tuple` of Python :class:`int`. Functions which work with percentage ``rgb()`` triplets accept them as 3-:class:`tuple` of Python strings (see :ref:`above regarding Python string types `). Plain tuples are accepted by all functions which deal with integer or percentage ``rgb()`` triplets, but three types of :func:`~collections.namedtuple` are also provided to represent these values: :class:`~webcolors.IntegerRGB` for integer triplets, :class:`~webcolors.PercentRGB` for percentage triplets, and :class:`~webcolors.HTML5SimpleColor` for an HTML5 simple color. Functions which return an integer or percentage ``rgb()`` triplet, or an HTML5 simple color, will return values of these types. Internally, Python :class:`float` is used in some conversions to and from the triplet representations; for each function which may have the precision of its results affected by this, a note is provided in the documentation. For colors specified via ``rgb()`` triplets, values contained in the triplets will be normalized in accordance with CSS clipping rules: * Integer values less than 0 will be normalized to 0, and percentage values less than 0% will be normalized to 0%. * Integer values greater than 255 will be normalized to 255, and percentage values greater than 100% will be normalized to 100%. * The "negative zero" values -0 and -0% will be normalized to 0 and 0%, respectively. The functions :func:`~webcolors.normalize_integer_triplet` and :func:`~webcolors.normalize_percent_triplet` can be used to perform this normalization manually if desired. .. _color-name-conventions: Color names ----------- For colors specified via predefined names, ``webcolors`` will accept strings containing names case-insensitively, so long as they contain no spaces or non-alphabetic characters. Thus, for example, ``"AliceBlue"`` and ``"aliceblue"`` are both accepted, and both will refer to the same color: ``rgb(240, 248, 255)``. For output which consists of a color name, and for functions which perform intermediate conversion to a predefined name before returning a result in another format, ``webcolors`` always normalizes such values to be entirely lowercase. .. note:: **Spelling variants** HTML 4, CSS1, and CSS2 each defined a color named ``"gray"``. In CSS3, this color can be named either ``"gray"`` or ``"grey"``, and several other related color values each have two names in CSS3: ``"darkgray"``/``"darkgrey"``, ``"darkslategray"``/``"darkslategrey"``, ``"dimgray"``/``"dimgrey"``, ``"lightgray"``/``"lightgrey"``, ``"lightslategray"``/``"lightslategrey"``, ``"slategray"``/``"slategrey"``. Reversing from the hexadecimal value, integer tuple, or percent tuple to a name, for these colors, requires picking one spelling, and ``webcolors`` chooses the ``"gray"`` spellings for consistency with HTML 4, CSS1, and CSS2. Identifying sets of named colors ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For purposes of identifying the specification from which to draw the selection of defined color names, ``webcolors`` uses strings naming the specifications, and provides :ref:`a set of constants containing the correct values `. Note that the CSS1 named colors are not supported here, as CSS1 merely "suggested" a set of color names, and declined to provide values for them. The CSS2 "system colors" are also not represented here, as they had no fixed defined values and are now deprecated. ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1761802443.416042 webcolors-25.10.0/docs/faq.rst0000644000000000000000000001425715100574313013045 0ustar00.. _faq: Frequently asked questions ========================== The following notes answer common questions, and may be useful to you when using ``webcolors``. General ------- What versions of Python are supported? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Version |release| of ``webcolors`` supports and is tested on Python 3.10, 3.11, 3.12, 3.13, and 3.14. How am I allowed to use this module? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``webcolors`` module is distributed under a `three-clause BSD license `_. This is an open-source license which grants you broad freedom to use, redistribute, modify and distribute modified versions of ``webcolors``. For details, see the file ``LICENSE`` in the source distribution of ``webcolors``. .. _three-clause BSD license: http://opensource.org/licenses/BSD-3-Clause I found a bug or want to make an improvement! ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The canonical development repository for ``webcolors`` is online at . Issues and pull requests can both be filed there. How closely does this module follow the standards? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ As closely as is practical (see below regarding floating-point values), within :ref:`the supported formats `; the ``webcolors`` module was written with the relevant standards documents close at hand. See :ref:`the conformance documentation ` for details. Design choices and technical details ------------------------------------ Why not use a more object-oriented design with classes for the colors? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Representing color values with Python classes would introduce overhead for no real gain. Real-world use cases tend to involve working directly with the actual values, so settling on conventions for how to represent them as Python types, and then offering a function-based interface, accomplishes everything needed without the additional indirection layer of having to instantiate and serialize a color-wrapping object. Keeping a function-based interface also maintains consistency with Python's built-in :mod:`colorsys` module which has the same style of interface for converting amongst color spaces. Note that if an object-oriented interface is desired, `the third-party colormath module `_ does have a class-based interface (and rightly so, as it offers a wider range of color representation and manipulation options than ``webcolors``). Why does ``webcolors`` prefer American spellings? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In CSS3, several color names are defined multiple times with identical values, to support both American and British spelling variants for ``"gray"``/``"grey"``. These colors are: ``"darkgray"``/``"darkgrey"``, ``"darkslategray"``/``"darkslategrey"``, ``"dimgray"``/``"dimgrey"``, ``"gray"``/``"grey"``, ``"lightgray"``/``"lightgrey"``, ``"lightslategray"``/``"lightslategrey"``, ``"slategray"``/``"slategrey"``. Using any of the conversions from names to other formats (:func:`~webcolors.name_to_hex`, :func:`~webcolors.name_to_rgb`, or :func:`~webcolors.name_to_rgb_percent`) will accept either spelling provided the `spec` argument is :data:`~webcolors.CSS3`. However, converting from other formats to a name requires choosing which spelling to return, and should return the same choice each time. So ``webcolors`` chooses the ``gray`` variants, for consistency with HTML 4, CSS1, and CSS2, each of which only allowed `gray`. Why aren't HSL values supported? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The :mod:`colorsys` module in the standard library contains functions for converting between RGB, HSL, HSV and YIQ color systems, so you can convert integer RGB triplets from ``webcolors`` to HSL triplets, or vice-versa, using :mod:`colorsys`, without ``webcolors`` needing to provide its own conversion functions. Why aren't ``rgb_to_rgb_percent()`` and ``rgb_percent_to_rgb()`` precise? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is due to limitations in the representation of floating-point numbers in programming languages. Python, like many programming languages, uses `IEEE floating-point `_, which is inherently imprecise for some values. This imprecision only appears when converting between integer and percentage ```rgb()``` triplets, as in :func:`~webcolors.rgb_to_rgb_percent` and :func:`~webcolors.rgb_percent_to_rgb`. To work around this, some common values (255, 128, 64, 32, 16 and 0) are handled as special cases, with hard-coded precise results. For all other values, conversion to percentage ``rgb()`` triplet uses a standard Python :class:`float`, rounding the result to two decimal places. See :ref:`the conformance documentation ` for details on how this affects testing. Are alpha-channel constructs like ``rgba()`` supported? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ While this decision may be re-evaluated in the future, ``webcolors`` currently does *not* support constructs which carry alpha-channel information (the ``rgba()`` and ``hsla()`` constructs of CSS3, or the ``#rrggbbaa`` construct of the CSS Colors Level 4 module). There are two main reasons for this: 1. ``webcolors`` does not yet support the CSS Color Module Level 4 in any way, which means the only supported construct would be ``rgba()`` (since ``webcolors`` only handles RGB color constructs, not HSL), and there would be no other alpha-channel construct to convert to or from. 2. Once support for the CSS Color Module Level 4 is finalized, it's still not clear that converting between ``rgba()`` and ``#rrggbbaa`` constructs would be useful enough on its own to justify the support. Converting to non-alpha-channel constructs would not require specialized functions since the alpha-channel component could simply be sliced off, and converting _from_ non-alpha-channel constructs to alpha-channel constructs similarly does not seem to require additional functions -- the desired alpha-channel information could be appended onto a non-alpha-channel construct easily enough. ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716874633.2190685 webcolors-25.10.0/docs/index.rst0000644000000000000000000000325214625266611013410 0ustar00webcolors |release| =================== ``webcolors`` is a module for working with and converting between the various HTML/CSS color formats. Support is included for normalizing and converting between the following formats (RGB colorspace only; conversion to/from HSL can be handled by the :mod:`colorsys` module in the Python standard library): * Specification-defined color names * Six-digit hexadecimal * Three-digit hexadecimal * Integer ``rgb()`` triplet * Percentage ``rgb()`` triplet For example: .. code-block:: python >>> import webcolors >>> webcolors.hex_to_name("#daa520") 'goldenrod' Implementations are also provided for the HTML5 color parsing and serialization algorithms. For example, parsing the infamous "chucknorris" string into an ``rgb()`` triplet: .. code-block:: python >>> import webcolors >>> webcolors.html5_parse_legacy_color("chucknorris") HTML5SimpleColor(red=192, blue=0, green=0) Documentation contents ---------------------- .. toctree:: :caption: Getting started :maxdepth: 1 install .. toctree:: :caption: Reference :maxdepth: 1 colors conventions contents conformance .. toctree:: :caption: Other documentation :maxdepth: 1 changelog faq .. seealso:: * `The sRGB color space `_ * `HTML 4: Colors `_ * `CSS1: Color units `_ * `CSS2: Colors `_ * `CSS3 color module `_ * `HTML5: Colors `_ ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1761802443.416151 webcolors-25.10.0/docs/install.rst0000644000000000000000000000364615100574313013744 0ustar00.. _install: Installation guide ================== Version |release| of ``webcolors`` is officially tested and supported on the following versions of Python: * Python 3.10 * Python 3.11 * Python 3.12 * Python 3.13 * Python 3.14 Installing ``webcolors`` ------------------------ To install the latest stable released version of ``webcolors``, run the following command from a command prompt/terminal: .. tab:: macOS/Linux/other Unix .. code-block:: shell python -m pip install --upgrade webcolors .. tab:: Windows .. code-block:: shell py -m pip install --upgrade webcolors This will use ``pip``, the standard Python package-installation tool. If you are using a supported version of Python, your installation of Python should have come with ``pip`` bundled. If ``pip`` does not appear to be present, you can try running the following from a command prompt/terminal: .. tab:: macOS/Linux/other Unix .. code-block:: shell python -m ensurepip --upgrade .. tab:: Windows .. code-block:: shell py -m ensurepip --upgrade Instructions are also available for `how to obtain and manually install or upgrade pip `_. Installing from a source checkout --------------------------------- If you want to work on ``webcolors``, you can obtain a source checkout. The development repository for ``webcolors`` is at . If you have `git `_ installed, you can obtain a copy of the repository by typing:: git clone https://github.com/ubernostrum/webcolors.git From there, you can use git commands to check out the specific revision you want, and perform an "editable" install (allowing you to change code as you work on it) by typing: .. tab:: macOS/Linux/other Unix .. code-block:: shell python -m pip install -e . .. tab:: Windows .. code-block:: shell py -m pip install -e . ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1378901941.0 webcolors-25.10.0/docs/make.bat0000644000000000000000000000562512214057665013161 0ustar00@ECHO OFF REM Command file for Sphinx documentation set SPHINXBUILD=sphinx-build set ALLSPHINXOPTS=-d _build/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (_build\*) do rmdir /q /s %%i del /q /s _build\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% _build/html echo. echo.Build finished. The HTML pages are in _build/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% _build/dirhtml echo. echo.Build finished. The HTML pages are in _build/dirhtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% _build/pickle echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% _build/json echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% _build/htmlhelp echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in _build/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% _build/qthelp echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in _build/qthelp, like this: echo.^> qcollectiongenerator _build\qthelp\webcolors.qhcp echo.To view the help file: echo.^> assistant -collectionFile _build\qthelp\webcolors.ghc goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% _build/latex echo. echo.Build finished; the LaTeX files are in _build/latex. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% _build/changes echo. echo.The overview file is in _build/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% _build/linkcheck echo. echo.Link check complete; look for any errors in the above output ^ or in _build/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% _build/doctest echo. echo.Testing of doctests in the sources finished, look at the ^ results in _build/doctest/output.txt. goto end ) :end ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761802443.4162452 webcolors-25.10.0/docs/requirements.txt0000644000000000000000000000037215100574313015021 0ustar00# These dependencies are not "extras" defined in the pyproject.toml, # because they're not something you actually want to install alongside # the main package. furo sphinx sphinx-copybutton sphinx-inline-tabs sphinx-notfound-page sphinxext-opengraph ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716183960.3927164 webcolors-25.10.0/docs/spelling_wordlist.txt0000644000000000000000000000041614622561630016047 0ustar00Changelog amongst bugfixes bytestrings changelog chucknorris codebase colorspace deprecations declaratively gz HSL identifiably incrementing internet losslessly nox online sRGB rgb submodule submodules superset trichromatic tuple tuples uncountably unprefixed versa whl ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761895008.1718912 webcolors-25.10.0/noxfile.py0000644000000000000000000002625115101061140012615 0ustar00""" Automated testing via nox (https://nox.thea.codes/). Combined with a working installation of nox (see ``nox`` documentation), this file specifies a matrix of tests, linters, and other quality checks which can be run individually or as a suite. To see available tasks, run ``python -m nox --list``. To run all available tasks -- which requires functioning installs of all supported Python versions -- run ``python -m nox``. To run a single task, use ``python -m nox --session`` with the name of that task. """ import os import pathlib import shutil import typing import nox nox.options.default_venv_backend = "venv" nox.options.reuse_existing_virtualenvs = True PACKAGE_NAME = "webcolors" IS_CI = bool(os.getenv("CI", False)) NOXFILE_PATH = pathlib.Path(__file__).parents[0] ARTIFACT_PATHS = ( NOXFILE_PATH / "src" / f"{PACKAGE_NAME}.egg-info", NOXFILE_PATH / "build", NOXFILE_PATH / "dist", NOXFILE_PATH / "__pycache__", NOXFILE_PATH / "src" / "__pycache__", NOXFILE_PATH / "src" / PACKAGE_NAME / "__pycache__", NOXFILE_PATH / "tests" / "__pycache__", ) def clean(paths: typing.Iterable[pathlib.Path] = ARTIFACT_PATHS) -> None: """ Clean up after a test run. """ # This cleanup is only useful for the working directory of a local checkout; in CI # we don't need it because CI environments are ephemeral anyway. if IS_CI: return [ shutil.rmtree(path) if path.is_dir() else path.unlink() for path in paths if path.exists() ] # Tasks which run the package's test suites. # ----------------------------------------------------------------------------------- @nox.session(python=["3.10", "3.11", "3.12", "3.13", "3.14"], tags=["tests"]) def tests_with_coverage(session: nox.Session) -> None: """ Run the package's unit tests, with coverage instrumentation. """ session.install(".", "pytest", "coverage[toml]") session.run( f"python{session.python}", "-Wonce::DeprecationWarning", "-Im", "coverage", "run", "-m", "pytest", "-vv", ) clean() @nox.session(python=["3.13"], tags=["tests"]) def coverage_report(session: nox.Session) -> None: """ Combine coverage from the various test runs and output the report. """ # In CI this job does not run because we substitute one that integrates with the CI # system. if IS_CI: session.skip( "Running in CI -- skipping nox coverage job in favor of CI coverage job" ) session.install("coverage[toml]") session.run(f"python{session.python}", "-Im", "coverage", "combine") session.run( f"python{session.python}", "-Im", "coverage", "report", "--show-missing" ) session.run(f"python{session.python}", "-Im", "coverage", "erase") @nox.session(python=["3.13"], tags=["release"]) def tests_definitions(session: nox.Session) -> None: """ Run the full color definitions test suite (requires an internet connection). """ if IS_CI: session.skip("Release tests do not run in CI.") session.install("pytest", "bs4", "html5lib", "requests", ".[tests]") session.run(f"python{session.python}", "-I", "tests/definitions.py") clean() @nox.session(python=["3.13"], tags=["release"]) def tests_full_colors(session: nox.Session) -> None: """ Run the full color conversion test suite (slow/CPU-intensive). """ if IS_CI: session.skip("Release tests do not run in CI.") session.install(".[tests]") session.run(f"python{session.python}", "-I", "tests/full_colors.py") clean() # Tasks which test the package's documentation. # ----------------------------------------------------------------------------------- # The documentation jobs ordinarily would want to use the latest Python version, but # currently that's 3.13 and Read The Docs doesn't yet support it. So to ensure the # documentation jobs are as closely matched to what would happen on RTD, these jobs stay # on 3.12 for now. @nox.session(python=["3.12"], tags=["docs"]) def docs_build(session: nox.Session) -> None: """ Build the package's documentation as HTML. """ session.install(".", "-r", "docs/requirements.txt") build_dir = session.create_tmp() session.run( f"{session.bin}/python{session.python}", "-Im", "sphinx", "--builder", "html", "--write-all", "-c", "docs/", "--doctree-dir", f"{build_dir}/doctrees", "docs/", f"{build_dir}/html", ) clean() @nox.session(python=["3.12"], tags=["docs"]) def docs_docstrings(session: nox.Session) -> None: """ Enforce the presence of docstrings on all modules, classes, functions, and methods. """ session.install("interrogate") session.run(f"python{session.python}", "-Im", "interrogate", "--version") session.run( f"python{session.python}", "-Im", "interrogate", "-v", "src/", "tests/", "noxfile.py", ) clean() @nox.session(python=["3.12"], tags=["docs"]) def docs_spellcheck(session: nox.Session) -> None: """ Spell-check the package's documentation. """ session.install(".", "-r", "docs/requirements.txt") session.install("pyenchant", "sphinxcontrib-spelling") build_dir = session.create_tmp() session.run( f"{session.bin}/python{session.python}", "-Im", "sphinx", "-W", # Promote warnings to errors, so that misspelled words fail the build. "--builder", "spelling", "-c", "docs/", "--doctree-dir", f"{build_dir}/doctrees", "docs/", f"{build_dir}/html", # On Apple Silicon Macs, this environment variable needs to be set so # pyenchant can find the "enchant" C library. See # https://github.com/pyenchant/pyenchant/issues/265#issuecomment-1126415843 env={"PYENCHANT_LIBRARY_PATH": os.getenv("PYENCHANT_LIBRARY_PATH", "")}, ) clean() # Code formatting checks. # # These checks do *not* reformat code -- that happens in pre-commit hooks -- but will # fail a CI build if they find any code that needs reformatting. # ----------------------------------------------------------------------------------- @nox.session(python=["3.13"], tags=["formatters"]) def format_black(session: nox.Session) -> None: """ Check code formatting with Black. """ session.install("black>=25.0,<26.0") session.run(f"python{session.python}", "-Im", "black", "--version") session.run( f"python{session.python}", "-Im", "black", "--check", "--diff", "src/", "tests/", "docs/", "noxfile.py", ) clean() @nox.session(python=["3.13"], tags=["formatters"]) def format_isort(session: nox.Session) -> None: """ Check import order with isort. """ session.install("isort") session.run(f"python{session.python}", "-Im", "isort", "--version") session.run( f"python{session.python}", "-Im", "isort", "--check-only", "--diff", "src/", "tests/", "docs/", "noxfile.py", ) clean() # Linters. # ----------------------------------------------------------------------------------- @nox.session(python=["3.13"], tags=["linters", "security"]) def lint_bandit(session: nox.Session) -> None: """ Lint code with the Bandit security analyzer. """ session.install("bandit[toml]") session.run(f"python{session.python}", "-Im", "bandit", "--version") session.run( f"python{session.python}", "-Im", "bandit", "-c", "./pyproject.toml", "-r", "src/", "tests/", ) clean() @nox.session(python=["3.13"], tags=["linters"]) def lint_flake8(session: nox.Session) -> None: """ Lint code with flake8. """ session.install("flake8", "flake8-bugbear", "flake8-pytest-style") session.run(f"python{session.python}", "-Im", "flake8", "--version") session.run( f"python{session.python}", "-Im", "flake8", "src/", "tests/", "docs/", "noxfile.py", ) clean() @nox.session(python=["3.13"], tags=["linters"]) def lint_pylint(session: nox.Session) -> None: """ Lint code with Pylint. """ # Pylint requires that all dependencies be importable during the run. session.install("pylint", "bs4", "html5lib", "requests", "pytest") session.run(f"python{session.python}", "-Im", "pylint", "--version") session.run(f"python{session.python}", "-Im", "pylint", "src/", "tests/") clean() # Packaging checks. # ----------------------------------------------------------------------------------- @nox.session(python=["3.13"], tags=["packaging"]) def package_build(session: nox.Session) -> None: """ Check that the package builds. """ session.install("build") session.run(f"python{session.python}", "-Im", "build", "--version") session.run(f"python{session.python}", "-Im", "build") clean() @nox.session(python=["3.13"], tags=["packaging"]) def package_description(session: nox.Session) -> None: """ Check that the package description will render on the Python Package Index. """ package_dir = session.create_tmp() session.install("build", "twine") session.run(f"python{session.python}", "-Im", "build", "--version") session.run(f"python{session.python}", "-Im", "twine", "--version") session.run( f"python{session.python}", "-Im", "build", "--wheel", "--outdir", f"{package_dir}/build", ) session.run( f"python{session.python}", "-Im", "twine", "check", f"{package_dir}/build/*" ) clean() @nox.session(python=["3.13"], tags=["packaging"]) def package_manifest(session: nox.Session) -> None: """ Check that the set of files in the package matches the set under version control. """ if IS_CI: session.skip("check-manifest already run by earlier CI steps.") session.install("check-manifest") session.run(f"python{session.python}", "-Im", "check_manifest", "--version") session.run(f"python{session.python}", "-Im", "check_manifest", "--verbose") clean() @nox.session(python=["3.13"], tags=["packaging"]) def package_pyroma(session: nox.Session) -> None: """ Check package quality with pyroma. """ session.install("pyroma") session.run(f"python{session.python}", "-Im", "pyroma", ".") clean() @nox.session(python=["3.13"], tags=["packaging"]) def package_wheel(session: nox.Session) -> None: """ Check the built wheel package for common errors. """ package_dir = session.create_tmp() session.install("build", "check-wheel-contents") session.run(f"python{session.python}", "-Im", "build", "--version") session.run(f"python{session.python}", "-Im", "check_wheel_contents", "--version") session.run( f"python{session.python}", "-Im", "build", "--wheel", "--outdir", f"{package_dir}/build", ) session.run( f"python{session.python}", "-Im", "check_wheel_contents", f"{package_dir}/build" ) clean() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761896733.8694928 webcolors-25.10.0/pdm.lock0000644000000000000000000011673215101064436012250 0ustar00# This file is @generated by PDM. # It is not intended for manual editing. [metadata] groups = ["default", "tests"] strategy = ["inherit_metadata"] lock_version = "4.5.0" content_hash = "sha256:2323895dc48b3076e6d637a2182257931777884e944da7dedcb1cc2f88140bec" [[metadata.targets]] requires_python = ">=3.10" [[package]] name = "colorama" version = "0.4.6" requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" summary = "Cross-platform colored terminal text." groups = ["tests"] marker = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "coverage" version = "7.11.0" requires_python = ">=3.10" summary = "Code coverage measurement for Python" groups = ["tests"] files = [ {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, ] [[package]] name = "coverage" version = "7.11.0" extras = ["toml"] requires_python = ">=3.10" summary = "Code coverage measurement for Python" groups = ["tests"] dependencies = [ "coverage==7.11.0", "tomli; python_full_version <= \"3.11.0a6\"", ] files = [ {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"}, {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"}, {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"}, {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"}, {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"}, {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"}, {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"}, {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"}, {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"}, {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"}, {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"}, {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"}, {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"}, {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"}, {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"}, {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"}, {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"}, {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"}, {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"}, {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"}, {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"}, {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"}, {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"}, {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"}, {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"}, {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"}, {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"}, {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"}, {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"}, {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"}, {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"}, {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"}, {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"}, {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"}, {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"}, {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"}, {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"}, {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"}, {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"}, {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"}, {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"}, {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"}, {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"}, {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"}, {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"}, {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"}, {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"}, {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"}, {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"}, {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"}, {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"}, {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"}, {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"}, {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"}, {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"}, {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"}, {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"}, {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"}, {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"}, {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"}, {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"}, {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"}, {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"}, {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"}, {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"}, ] [[package]] name = "exceptiongroup" version = "1.3.0" requires_python = ">=3.7" summary = "Backport of PEP 654 (exception groups)" groups = ["tests"] marker = "python_version < \"3.11\"" dependencies = [ "typing-extensions>=4.6.0; python_version < \"3.13\"", ] files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, ] [[package]] name = "iniconfig" version = "2.3.0" requires_python = ">=3.10" summary = "brain-dead simple config-ini parsing" groups = ["tests"] files = [ {file = "iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12"}, {file = "iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730"}, ] [[package]] name = "packaging" version = "25.0" requires_python = ">=3.8" summary = "Core utilities for Python packages" groups = ["tests"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] name = "pluggy" version = "1.6.0" requires_python = ">=3.9" summary = "plugin and hook calling mechanisms for python" groups = ["tests"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [[package]] name = "pygments" version = "2.19.2" requires_python = ">=3.8" summary = "Pygments is a syntax highlighting package written in Python." groups = ["tests"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [[package]] name = "pytest" version = "8.4.2" requires_python = ">=3.9" summary = "pytest: simple powerful testing with Python" groups = ["tests"] dependencies = [ "colorama>=0.4; sys_platform == \"win32\"", "exceptiongroup>=1; python_version < \"3.11\"", "iniconfig>=1", "packaging>=20", "pluggy<2,>=1.5", "pygments>=2.7.2", "tomli>=1; python_version < \"3.11\"", ] files = [ {file = "pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79"}, {file = "pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01"}, ] [[package]] name = "tomli" version = "2.3.0" requires_python = ">=3.8" summary = "A lil' TOML parser" groups = ["tests"] marker = "python_version < \"3.11\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] [[package]] name = "typing-extensions" version = "4.15.0" requires_python = ">=3.9" summary = "Backported and Experimental Type Hints for Python 3.9+" groups = ["tests"] marker = "python_version < \"3.11\"" files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1761897043.454185 webcolors-25.10.0/pyproject.toml0000644000000000000000000000410015101065123013505 0ustar00[build-system] requires = [ "pdm-backend", ] build-backend = "pdm.backend" [project] authors = [ { name = "James Bennett" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", "Topic :: Utilities", ] name = "webcolors" description = "A library for working with the color formats defined by HTML and CSS." keywords = [ "color", "css", "html", "web", ] readme = "README.rst" requires-python = ">=3.10" version = "25.10.0" [project.license] text = "BSD-3-Clause" [project.urls] Documentation = "https://webcolors.readthedocs.io" "Source Code" = "https://github.com/ubernostrum/webcolors" [dependency-groups] tests = [ "coverage[toml]", "pytest", ] [tool.bandit] skips = [ "B101", ] [tool.black] target-version = [ "py310", "py311", "py312", "py313", "py314", ] [tool.coverage.paths] source = [ "src", ".nox/tests_with_coverage*/**/site-packages", ] [tool.coverage.report] fail_under = 100 omit = [ "tests/definitions.py", "tests/full_colors.py", ] [tool.coverage.run] branch = true parallel = true source = [ "tests", "webcolors", ] [tool.interrogate] fail-under = 100 ignore-init-method = true ignore-init-module = true [tool.isort] profile = "black" [tool.pdm] distribution = true [tool.pdm.build] source-includes = [ ".editorconfig", ".flake8", ".pre-commit-config.yaml", ".readthedocs.yaml", "AUTHORS", "CONTRIBUTING.rst", "docs/", "Makefile", "noxfile.py", "pdm.lock", "runtests.py", "tests/", ] [tool.pylint] disable = [ "duplicate-code", "protected-access", ] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1731310719.435372 webcolors-25.10.0/src/webcolors/__init__.py0000644000000000000000000000310414714332177015512 0ustar00""" Functions for working with the color names and color value formats defined by the HTML and CSS specifications for use in documents on the web. See documentation (in docs/ directory of source distribution) for details of the supported formats, conventions and conversions. """ # SPDX-License-Identifier: BSD-3-Clause from ._conversion import ( hex_to_name, hex_to_rgb, hex_to_rgb_percent, name_to_hex, name_to_rgb, name_to_rgb_percent, rgb_percent_to_hex, rgb_percent_to_name, rgb_percent_to_rgb, rgb_to_hex, rgb_to_name, rgb_to_rgb_percent, ) from ._definitions import CSS2, CSS3, CSS21, HTML4, names from ._html5 import ( html5_parse_legacy_color, html5_parse_simple_color, html5_serialize_simple_color, ) from ._normalization import ( normalize_hex, normalize_integer_triplet, normalize_percent_triplet, ) from ._types import HTML5SimpleColor, IntegerRGB, IntTuple, PercentRGB, PercentTuple __all__ = [ "HTML4", "CSS2", "CSS21", "CSS3", "name_to_hex", "name_to_rgb", "name_to_rgb_percent", "hex_to_name", "hex_to_rgb", "hex_to_rgb_percent", "names", "rgb_to_hex", "rgb_to_name", "rgb_to_rgb_percent", "rgb_percent_to_hex", "rgb_percent_to_name", "rgb_percent_to_rgb", "html5_parse_simple_color", "html5_parse_legacy_color", "html5_serialize_simple_color", "normalize_hex", "normalize_integer_triplet", "normalize_percent_triplet", "IntegerRGB", "PercentRGB", "HTML5SimpleColor", "IntTuple", "PercentTuple", ] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1723013296.0554945 webcolors-25.10.0/src/webcolors/_conversion.py0000644000000000000000000003206114654614260016302 0ustar00""" Functions which convert between various types of color values. """ # SPDX-License-Identifier: BSD-3-Clause from ._definitions import CSS3, _get_hex_to_name_map, _get_name_to_hex_map from ._normalization import ( _percent_to_integer, normalize_hex, normalize_integer_triplet, normalize_percent_triplet, ) from ._types import IntegerRGB, IntTuple, PercentRGB, PercentTuple # Conversions from color names to other formats. # -------------------------------------------------------------------------------- def name_to_hex(name: str, spec: str = CSS3) -> str: """ Convert a color name to a normalized hexadecimal color value. The color name will be normalized to lower-case before being looked up. Examples: .. doctest:: >>> name_to_hex("white") '#ffffff' >>> name_to_hex("navy") '#000080' >>> name_to_hex("goldenrod") '#daa520' >>> name_to_hex("goldenrod", spec=HTML4) Traceback (most recent call last): ... ValueError: "goldenrod" is not defined as a named color in html4. :param name: The color name to convert. :param spec: The specification from which to draw the list of color names. Default is :data:`CSS3`. :raises ValueError: when the given name has no definition in the given spec. """ color_map = _get_name_to_hex_map(spec) if hex_value := color_map.get(name.lower()): return hex_value raise ValueError(f'"{name}" is not defined as a named color in {spec}') def name_to_rgb(name: str, spec: str = CSS3) -> IntegerRGB: """ Convert a color name to a 3-:class:`tuple` of :class:`int` suitable for use in an ``rgb()`` triplet specifying that color. The color name will be normalized to lower-case before being looked up. Examples: .. doctest:: >>> name_to_rgb("white") IntegerRGB(red=255, green=255, blue=255) >>> name_to_rgb("navy") IntegerRGB(red=0, green=0, blue=128) >>> name_to_rgb("goldenrod") IntegerRGB(red=218, green=165, blue=32) :param name: The color name to convert. :param spec: The specification from which to draw the list of color names. Default is :data:`CSS3.` :raises ValueError: when the given name has no definition in the given spec. """ return hex_to_rgb(name_to_hex(name, spec=spec)) def name_to_rgb_percent(name: str, spec: str = CSS3) -> PercentRGB: """ Convert a color name to a 3-:class:`tuple` of percentages suitable for use in an ``rgb()`` triplet specifying that color. The color name will be normalized to lower-case before being looked up. Examples: .. doctest:: >>> name_to_rgb_percent("white") PercentRGB(red='100%', green='100%', blue='100%') >>> name_to_rgb_percent("navy") PercentRGB(red='0%', green='0%', blue='50%') >>> name_to_rgb_percent("goldenrod") PercentRGB(red='85.49%', green='64.71%', blue='12.5%') :param name: The color name to convert. :param spec: The specification from which to draw the list of color names. Default is :data:`CSS3`. :raises ValueError: when the given name has no definition in the given spec. """ return rgb_to_rgb_percent(name_to_rgb(name, spec=spec)) # Conversions from hexadecimal color values to other formats. # -------------------------------------------------------------------------------- def hex_to_name(hex_value: str, spec: str = CSS3) -> str: """ Convert a hexadecimal color value to its corresponding normalized color name, if any such name exists. The hexadecimal value will be normalized before being looked up. .. note:: **Spelling variants** Some values representing named gray colors can map to either of two names in CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for those colors. This function will always return the variant spelled ``"gray"`` (such as ``"lightgray"`` instead of ``"lightgrey"``). See :ref:`the documentation on name conventions ` for details. Examples: .. doctest:: >>> hex_to_name("#ffffff") 'white' >>> hex_to_name("#fff") 'white' >>> hex_to_name("#000080") 'navy' >>> hex_to_name("#daa520") 'goldenrod' >>> hex_to_name("#daa520", spec=HTML4) Traceback (most recent call last): ... ValueError: "#daa520" has no defined color name in html4. :param hex_value: The hexadecimal color value to convert. :param spec: The specification from which to draw the list of color names. Default is :data:`CSS3`. :raises ValueError: when the given color has no name in the given spec, or when the supplied hex value is invalid. """ color_map = _get_hex_to_name_map(spec) if name := color_map.get(normalize_hex(hex_value)): return name raise ValueError(f'"{hex_value}" has no defined color name in {spec}.') def hex_to_rgb(hex_value: str) -> IntegerRGB: """ Convert a hexadecimal color value to a 3-:class:`tuple` of :class:`int` suitable for use in an ``rgb()`` triplet specifying that color. The hexadecimal value will be normalized before being converted. Examples: .. doctest:: >>> hex_to_rgb("#fff") IntegerRGB(red=255, green=255, blue=255) >>> hex_to_rgb("#000080") IntegerRGB(red=0, green=0, blue=128) :param hex_value: The hexadecimal color value to convert. :raises ValueError: when the supplied hex value is invalid. """ int_value = int(normalize_hex(hex_value)[1:], 16) return IntegerRGB(int_value >> 16, int_value >> 8 & 0xFF, int_value & 0xFF) def hex_to_rgb_percent(hex_value: str) -> PercentRGB: """ Convert a hexadecimal color value to a 3-:class:`tuple` of percentages suitable for use in an ``rgb()`` triplet representing that color. The hexadecimal value will be normalized before being converted. Examples: .. doctest:: >>> hex_to_rgb_percent("#ffffff") PercentRGB(red='100%', green='100%', blue='100%') >>> hex_to_rgb_percent("#000080") PercentRGB(red='0%', green='0%', blue='50%') :param hex_value: The hexadecimal color value to convert. :raises ValueError: when the supplied hex value is invalid. """ return rgb_to_rgb_percent(hex_to_rgb(hex_value)) # Conversions from integer rgb() triplets to other formats. # -------------------------------------------------------------------------------- def rgb_to_name(rgb_triplet: IntTuple, spec: str = CSS3) -> str: """ Convert a 3-:class:`tuple` of :class:`int`, suitable for use in an ``rgb()`` color triplet, to its corresponding normalized color name, if any such name exists. To determine the name, the triplet will be converted to a normalized hexadecimal value. .. note:: **Spelling variants** Some values representing named gray colors can map to either of two names in CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for those colors. This function will always return the variant spelled ``"gray"`` (such as ``"lightgray"`` instead of ``"lightgrey"``). See :ref:`the documentation on name conventions ` for details. Examples: .. doctest:: >>> rgb_to_name((255, 255, 255)) 'white' >>> rgb_to_name((0, 0, 128)) 'navy' :param rgb_triplet: The ``rgb()`` triplet. :param spec: The specification from which to draw the list of color names. Default is :data:`CSS3`. :raises ValueError: when the given color has no name in the given spec. """ return hex_to_name(rgb_to_hex(normalize_integer_triplet(rgb_triplet)), spec=spec) def rgb_to_hex(rgb_triplet: IntTuple) -> str: """ Convert a 3-:class:`tuple` of :class:`int`, suitable for use in an ``rgb()`` color triplet, to a normalized hexadecimal value for that color. Examples: .. doctest:: >>> rgb_to_hex((255, 255, 255)) '#ffffff' >>> rgb_to_hex((0, 0, 128)) '#000080' :param rgb_triplet: The ``rgb()`` triplet. """ red, green, blue = normalize_integer_triplet(rgb_triplet) return f"#{red:02x}{green:02x}{blue:02x}" def rgb_to_rgb_percent(rgb_triplet: IntTuple) -> PercentRGB: """ Convert a 3-:class:`tuple` of :class:`int`, suitable for use in an ``rgb()`` color triplet, to a 3-:class:`tuple` of percentages suitable for use in representing that color. .. note:: **Floating-point precision** This function makes some trade-offs in terms of the accuracy of the final representation. For some common integer values, special-case logic is used to ensure a precise result (e.g., integer 128 will always convert to ``"50%"``, integer 32 will always convert to ``"12.5%"``), but for all other values a standard Python :class:`float` is used and rounded to two decimal places, which may result in a loss of precision for some values due to the inherent imprecision of `IEEE floating-point numbers `_. Examples: .. doctest:: >>> rgb_to_rgb_percent((255, 255, 255)) PercentRGB(red='100%', green='100%', blue='100%') >>> rgb_to_rgb_percent((0, 0, 128)) PercentRGB(red='0%', green='0%', blue='50%') >>> rgb_to_rgb_percent((218, 165, 32)) PercentRGB(red='85.49%', green='64.71%', blue='12.5%') :param rgb_triplet: The ``rgb()`` triplet. """ # In order to maintain precision for common values, # special-case them. specials = { 255: "100%", 128: "50%", 64: "25%", 32: "12.5%", 16: "6.25%", 0: "0%", } return PercentRGB._make( specials.get(d, f"{d / 255.0 * 100:.02f}%") for d in normalize_integer_triplet(rgb_triplet) ) # Conversions from percentage rgb() triplets to other formats. # -------------------------------------------------------------------------------- def rgb_percent_to_name(rgb_percent_triplet: PercentTuple, spec: str = CSS3) -> str: """ Convert a 3-:class:`tuple` of percentages, suitable for use in an ``rgb()`` color triplet, to its corresponding normalized color name, if any such name exists. To determine the name, the triplet will be converted to a normalized hexadecimal value. .. note:: **Spelling variants** Some values representing named gray colors can map to either of two names in CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for those colors. This function will always return the variant spelled ``"gray"`` (such as ``"lightgray"`` instead of ``"lightgrey"``). See :ref:`the documentation on name conventions ` for details. Examples: .. doctest:: >>> rgb_percent_to_name(("100%", "100%", "100%")) 'white' >>> rgb_percent_to_name(("0%", "0%", "50%")) 'navy' >>> rgb_percent_to_name(("85.49%", "64.71%", "12.5%")) 'goldenrod' :param rgb_percent_triplet: The ``rgb()`` triplet. :param spec: The specification from which to draw the list of color names. Default is :data:`CSS3`. :raises ValueError: when the given color has no name in the given spec. """ return rgb_to_name( rgb_percent_to_rgb(normalize_percent_triplet(rgb_percent_triplet)), spec=spec, ) def rgb_percent_to_hex(rgb_percent_triplet: PercentTuple) -> str: """ Convert a 3-:class:`tuple` of percentages, suitable for use in an ``rgb()`` color triplet, to a normalized hexadecimal color value for that color. Examples: .. doctest:: >>> rgb_percent_to_hex(("100%", "100%", "0%")) '#ffff00' >>> rgb_percent_to_hex(("0%", "0%", "50%")) '#000080' >>> rgb_percent_to_hex(("85.49%", "64.71%", "12.5%")) '#daa520' :param rgb_percent_triplet: The ``rgb()`` triplet. """ return rgb_to_hex( rgb_percent_to_rgb(normalize_percent_triplet(rgb_percent_triplet)) ) def rgb_percent_to_rgb(rgb_percent_triplet: PercentTuple) -> IntegerRGB: """ Convert a 3-:class:`tuple` of percentages, suitable for use in an ``rgb()`` color triplet, to a 3-:class:`tuple` of :class:`int` suitable for use in representing that color. Some precision may be lost in this conversion. See the note regarding precision for :func:`~webcolors.rgb_to_rgb_percent` for details. Examples: .. doctest:: >>> rgb_percent_to_rgb(("100%", "100%", "100%")) IntegerRGB(red=255, green=255, blue=255) >>> rgb_percent_to_rgb(("0%", "0%", "50%")) IntegerRGB(red=0, green=0, blue=128) >>> rgb_percent_to_rgb(("85.49%", "64.71%", "12.5%")) IntegerRGB(red=218, green=165, blue=32) :param rgb_percent_triplet: The ``rgb()`` triplet. """ return IntegerRGB._make( map( _percent_to_integer, # pylint: disable=protected-access normalize_percent_triplet(rgb_percent_triplet), ) ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761526808.9985108 webcolors-25.10.0/src/webcolors/_definitions.py0000644000000000000000000002404115077542031016423 0ustar00""" Definitions of valid formats and values for colors. """ # SPDX-License-Identifier: BSD-3-Clause import re from typing import List def _reversedict(dict_to_reverse: dict) -> dict: """ Internal helper for generating reverse mappings; given a dictionary, returns a new dictionary with keys and values swapped. """ return {value: key for key, value in dict_to_reverse.items()} _HEX_COLOR_RE = re.compile(r"^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$") HTML4 = "html4" CSS2 = "css2" CSS21 = "css21" CSS3 = "css3" _SUPPORTED_SPECIFICATIONS = (HTML4, CSS2, CSS21, CSS3) _SPECIFICATION_ERROR_TEMPLATE = ( f"{{spec}} is not a supported specification for color name lookups; " f"supported specifications are: {_SUPPORTED_SPECIFICATIONS}." ) # Mappings of color names to normalized hexadecimal color values. # -------------------------------------------------------------------------------- # The HTML 4 named colors. # # The canonical source for these color definitions is the HTML 4 specification: # # http://www.w3.org/TR/html401/types.html#h-6.5 # # The file tests/definitions.py in the source distribution of this module downloads a # copy of the HTML 4 standard and parses out the color names to ensure the values below # are correct. _HTML4_NAMES_TO_HEX = { "aqua": "#00ffff", "black": "#000000", "blue": "#0000ff", "fuchsia": "#ff00ff", "green": "#008000", "gray": "#808080", "lime": "#00ff00", "maroon": "#800000", "navy": "#000080", "olive": "#808000", "purple": "#800080", "red": "#ff0000", "silver": "#c0c0c0", "teal": "#008080", "white": "#ffffff", "yellow": "#ffff00", } # CSS2 used the same list as HTML 4. _CSS2_NAMES_TO_HEX = _HTML4_NAMES_TO_HEX # CSS2.1 added orange. _CSS21_NAMES_TO_HEX = {"orange": "#ffa500", **_HTML4_NAMES_TO_HEX} # The CSS3/SVG named colors. # # The canonical source for these color definitions is the SVG specification's color list # (which was adopted as CSS 3's color definition): # # http://www.w3.org/TR/SVG11/types.html#ColorKeywords # # CSS3 also provides definitions of these colors: # # http://www.w3.org/TR/css3-color/#svg-color # # SVG provides the definitions as RGB triplets. CSS3 provides them both as RGB triplets # and as hexadecimal. Since hex values are more common in real-world HTML and CSS, the # mapping below is to hex values instead. The file tests/definitions.py in the source # distribution of this module downloads a copy of the CSS3 color module and parses out # the color names to ensure the values below are correct. _CSS3_NAMES_TO_HEX = { "aliceblue": "#f0f8ff", "antiquewhite": "#faebd7", "aqua": "#00ffff", "aquamarine": "#7fffd4", "azure": "#f0ffff", "beige": "#f5f5dc", "bisque": "#ffe4c4", "black": "#000000", "blanchedalmond": "#ffebcd", "blue": "#0000ff", "blueviolet": "#8a2be2", "brown": "#a52a2a", "burlywood": "#deb887", "cadetblue": "#5f9ea0", "chartreuse": "#7fff00", "chocolate": "#d2691e", "coral": "#ff7f50", "cornflowerblue": "#6495ed", "cornsilk": "#fff8dc", "crimson": "#dc143c", "cyan": "#00ffff", "darkblue": "#00008b", "darkcyan": "#008b8b", "darkgoldenrod": "#b8860b", "darkgray": "#a9a9a9", "darkgrey": "#a9a9a9", "darkgreen": "#006400", "darkkhaki": "#bdb76b", "darkmagenta": "#8b008b", "darkolivegreen": "#556b2f", "darkorange": "#ff8c00", "darkorchid": "#9932cc", "darkred": "#8b0000", "darksalmon": "#e9967a", "darkseagreen": "#8fbc8f", "darkslateblue": "#483d8b", "darkslategray": "#2f4f4f", "darkslategrey": "#2f4f4f", "darkturquoise": "#00ced1", "darkviolet": "#9400d3", "deeppink": "#ff1493", "deepskyblue": "#00bfff", "dimgray": "#696969", "dimgrey": "#696969", "dodgerblue": "#1e90ff", "firebrick": "#b22222", "floralwhite": "#fffaf0", "forestgreen": "#228b22", "fuchsia": "#ff00ff", "gainsboro": "#dcdcdc", "ghostwhite": "#f8f8ff", "gold": "#ffd700", "goldenrod": "#daa520", "gray": "#808080", "grey": "#808080", "green": "#008000", "greenyellow": "#adff2f", "honeydew": "#f0fff0", "hotpink": "#ff69b4", "indianred": "#cd5c5c", "indigo": "#4b0082", "ivory": "#fffff0", "khaki": "#f0e68c", "lavender": "#e6e6fa", "lavenderblush": "#fff0f5", "lawngreen": "#7cfc00", "lemonchiffon": "#fffacd", "lightblue": "#add8e6", "lightcoral": "#f08080", "lightcyan": "#e0ffff", "lightgoldenrodyellow": "#fafad2", "lightgray": "#d3d3d3", "lightgrey": "#d3d3d3", "lightgreen": "#90ee90", "lightpink": "#ffb6c1", "lightsalmon": "#ffa07a", "lightseagreen": "#20b2aa", "lightskyblue": "#87cefa", "lightslategray": "#778899", "lightslategrey": "#778899", "lightsteelblue": "#b0c4de", "lightyellow": "#ffffe0", "lime": "#00ff00", "limegreen": "#32cd32", "linen": "#faf0e6", "magenta": "#ff00ff", "maroon": "#800000", "mediumaquamarine": "#66cdaa", "mediumblue": "#0000cd", "mediumorchid": "#ba55d3", "mediumpurple": "#9370db", "mediumseagreen": "#3cb371", "mediumslateblue": "#7b68ee", "mediumspringgreen": "#00fa9a", "mediumturquoise": "#48d1cc", "mediumvioletred": "#c71585", "midnightblue": "#191970", "mintcream": "#f5fffa", "mistyrose": "#ffe4e1", "moccasin": "#ffe4b5", "navajowhite": "#ffdead", "navy": "#000080", "oldlace": "#fdf5e6", "olive": "#808000", "olivedrab": "#6b8e23", "orange": "#ffa500", "orangered": "#ff4500", "orchid": "#da70d6", "palegoldenrod": "#eee8aa", "palegreen": "#98fb98", "paleturquoise": "#afeeee", "palevioletred": "#db7093", "papayawhip": "#ffefd5", "peachpuff": "#ffdab9", "peru": "#cd853f", "pink": "#ffc0cb", "plum": "#dda0dd", "powderblue": "#b0e0e6", "purple": "#800080", "red": "#ff0000", "rosybrown": "#bc8f8f", "royalblue": "#4169e1", "saddlebrown": "#8b4513", "salmon": "#fa8072", "sandybrown": "#f4a460", "seagreen": "#2e8b57", "seashell": "#fff5ee", "sienna": "#a0522d", "silver": "#c0c0c0", "skyblue": "#87ceeb", "slateblue": "#6a5acd", "slategray": "#708090", "slategrey": "#708090", "snow": "#fffafa", "springgreen": "#00ff7f", "steelblue": "#4682b4", "tan": "#d2b48c", "teal": "#008080", "thistle": "#d8bfd8", "tomato": "#ff6347", "turquoise": "#40e0d0", "violet": "#ee82ee", "wheat": "#f5deb3", "white": "#ffffff", "whitesmoke": "#f5f5f5", "yellow": "#ffff00", "yellowgreen": "#9acd32", } # Mappings of normalized hexadecimal color values to color names. # -------------------------------------------------------------------------------- _HTML4_HEX_TO_NAMES = _reversedict(_HTML4_NAMES_TO_HEX) _CSS2_HEX_TO_NAMES = _HTML4_HEX_TO_NAMES _CSS21_HEX_TO_NAMES = _reversedict(_CSS21_NAMES_TO_HEX) _CSS3_HEX_TO_NAMES = _reversedict(_CSS3_NAMES_TO_HEX) # CSS3 defines both "gray" and "grey", as well as defining either spelling variant for # other related colors like "darkgray"/"darkgrey", etc. For a "forward" lookup from # name to hex, this is straightforward, but a "reverse" lookup from hex to name requires # picking one spelling and being consistent about it. # # Since "gray" was the only spelling supported in HTML 4, CSS1, and CSS2, "gray" and its # variants are chosen here. _CSS3_HEX_TO_NAMES["#a9a9a9"] = "darkgray" _CSS3_HEX_TO_NAMES["#2f4f4f"] = "darkslategray" _CSS3_HEX_TO_NAMES["#696969"] = "dimgray" _CSS3_HEX_TO_NAMES["#808080"] = "gray" _CSS3_HEX_TO_NAMES["#d3d3d3"] = "lightgray" _CSS3_HEX_TO_NAMES["#778899"] = "lightslategray" _CSS3_HEX_TO_NAMES["#708090"] = "slategray" _names_to_hex = { HTML4: _HTML4_NAMES_TO_HEX, CSS2: _CSS2_NAMES_TO_HEX, CSS21: _CSS21_NAMES_TO_HEX, CSS3: _CSS3_NAMES_TO_HEX, } _hex_to_names = { HTML4: _HTML4_HEX_TO_NAMES, CSS2: _CSS2_HEX_TO_NAMES, CSS21: _CSS21_HEX_TO_NAMES, CSS3: _CSS3_HEX_TO_NAMES, } def _get_name_to_hex_map(spec: str) -> dict: """ Return the name-to-hex mapping for the given specification. :raises ValueError: when the given spec is not supported. """ if spec not in _SUPPORTED_SPECIFICATIONS: raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec)) return _names_to_hex[spec] def _get_hex_to_name_map(spec: str) -> dict: """ Return the hex-to-name mapping for the given specification. :raises ValueError: when the given spec is not supported. """ if spec not in _SUPPORTED_SPECIFICATIONS: raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec)) return _hex_to_names[spec] def names(spec: str = CSS3) -> List[str]: """ Return the list of valid color names for the given specification. The color names will be normalized to all-lowercase, and will be returned in alphabetical order. .. note:: **Spelling variants** Some values representing named gray colors can map to either of two names in CSS3, because it supports both ``"gray"`` and ``"grey"`` spelling variants for those colors. Functions which produce a name from a color value in other formats all normalize to the ``"gray"`` spelling for consistency with earlier CSS and HTML specifications which only supported ``"gray"``. Here, however, *all* valid names are returned, including -- for CSS3 -- both variant spellings for each of the affected ``"gray"``/``"grey"`` colors. Examples: .. doctest:: >>> names(spec=HTML4) ['aqua', 'black', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'purple', 'red', 'silver', 'teal', 'white', 'yellow'] >>> names(spec="CSS1") Traceback (most recent call last): ... ValueError: "CSS1" is not a supported specification ... :raises ValueError: when the given spec is not supported. """ if spec not in _SUPPORTED_SPECIFICATIONS: raise ValueError(_SPECIFICATION_ERROR_TEMPLATE.format(spec=spec)) mapping = _names_to_hex[spec] return list(sorted(mapping.keys())) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761526748.3699262 webcolors-25.10.0/src/webcolors/_html5.py0000644000000000000000000002260215077541734015153 0ustar00""" HTML5 color algorithms. Note that these functions are written in a way that may seem strange to developers familiar with Python, because they do not use the most efficient or idiomatic way of accomplishing their tasks. This is because, for compliance, these functions are written as literal translations into Python of the algorithms in HTML5: https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#colours For ease of understanding, the relevant steps of the algorithm from the standard are included as comments interspersed in the implementation. """ # SPDX-License-Identifier: BSD-3-Clause import string from ._definitions import _CSS3_NAMES_TO_HEX from ._types import HTML5SimpleColor, IntTuple def html5_parse_simple_color(value: str) -> HTML5SimpleColor: """ Apply the HTML5 simple color parsing algorithm. Examples: .. doctest:: >>> html5_parse_simple_color("#ffffff") HTML5SimpleColor(red=255, green=255, blue=255) >>> html5_parse_simple_color("#fff") Traceback (most recent call last): ... ValueError: An HTML5 simple color must be a string seven characters long. :param value: The color to parse. :type value: :class:`str`, which must consist of exactly the character ``"#"`` followed by six hexadecimal digits. :raises ValueError: when the given value is not a Unicode string of length 7, consisting of exactly the character ``#`` followed by six hexadecimal digits. """ # 1. Let input be the string being parsed. # # 2. If input is not exactly seven characters long, then return an error. if not isinstance(value, str) or len(value) != 7: raise ValueError( "An HTML5 simple color must be a Unicode string seven characters long." ) # 3. If the first character in input is not a U+0023 NUMBER SIGN character (#), then # return an error. if not value.startswith("#"): raise ValueError( "An HTML5 simple color must begin with the character '#' (U+0023)." ) # 4. If the last six characters of input are not all ASCII hex digits, then return # an error. if not all(c in string.hexdigits for c in value[1:]): raise ValueError( "An HTML5 simple color must contain exactly six ASCII hex digits." ) # 5. Let result be a simple color. # # 6. Interpret the second and third characters as a hexadecimal number and let the # result be the red component of result. # # 7. Interpret the fourth and fifth characters as a hexadecimal number and let the # result be the green component of result. # # 8. Interpret the sixth and seventh characters as a hexadecimal number and let the # result be the blue component of result. # # 9. Return result. return HTML5SimpleColor( int(value[1:3], 16), int(value[3:5], 16), int(value[5:7], 16) ) def html5_serialize_simple_color(simple_color: IntTuple) -> str: """ Apply the HTML5 simple color serialization algorithm. Examples: .. doctest:: >>> html5_serialize_simple_color((0, 0, 0)) '#000000' >>> html5_serialize_simple_color((255, 255, 255)) '#ffffff' :param simple_color: The color to serialize. """ red, green, blue = simple_color # 1. Let result be a string consisting of a single "#" (U+0023) character. # # 2. Convert the red, green, and blue components in turn to two-digit hexadecimal # numbers using lowercase ASCII hex digits, zero-padding if necessary, and append # these numbers to result, in the order red, green, blue. # # 3. Return result, which will be a valid lowercase simple color. return f"#{red:02x}{green:02x}{blue:02x}" def html5_parse_legacy_color(value: str) -> HTML5SimpleColor: """ Apply the HTML5 legacy color parsing algorithm. Note that, since this algorithm is intended to handle many types of malformed color values present in real-world Web documents, it is *extremely* forgiving of input, but the results of parsing inputs with high levels of "junk" (i.e., text other than a color value) may be surprising. Examples: .. doctest:: >>> html5_parse_legacy_color("black") HTML5SimpleColor(red=0, green=0, blue=0) >>> html5_parse_legacy_color("chucknorris") HTML5SimpleColor(red=192, green=0, blue=0) >>> html5_parse_legacy_color("Window") HTML5SimpleColor(red=0, green=13, blue=0) :param value: The color to parse. :raises ValueError: when the given value is not a Unicode string, when it is the empty string, or when it is precisely the string ``"transparent"``. """ # 1. Let input be the string being parsed. if not isinstance(value, str): raise ValueError( "HTML5 legacy color parsing requires a Unicode string as input." ) # 2. If input is the empty string, then return an error. if value == "": raise ValueError("HTML5 legacy color parsing forbids empty string as a value.") # 3. Strip leading and trailing ASCII whitespace from input. value = value.strip() # 4. If input is an ASCII case-insensitive match for the string "transparent", then # return an error. if value.lower() == "transparent": raise ValueError('HTML5 legacy color parsing forbids "transparent" as a value.') # 5. If input is an ASCII case-insensitive match for one of the named colors, then # return the simple color corresponding to that keyword. # # Note: CSS2 System Colors are not recognized. if keyword_hex := _CSS3_NAMES_TO_HEX.get(value.lower()): return html5_parse_simple_color(keyword_hex) # 6. If input's code point length is four, and the first character in input is # U+0023 (#), and the last three characters of input are all ASCII hex digits, # then: if ( len(value) == 4 and value.startswith("#") and all(c in string.hexdigits for c in value[1:]) ): # 1. Let result be a simple color. # # 2. Interpret the second character of input as a hexadecimal digit; let the red # component of result be the resulting number multiplied by 17. # # 3. Interpret the third character of input as a hexadecimal digit; let the # green component of result be the resulting number multiplied by 17. # # 4. Interpret the fourth character of input as a hexadecimal digit; let the # blue component of result be the resulting number multiplied by 17. result = HTML5SimpleColor( int(value[1], 16) * 17, int(value[2], 16) * 17, int(value[3], 16) * 17 ) # 5. Return result. return result # 7. Replace any code points greater than U+FFFF in input (i.e., any characters that # are not in the basic multilingual plane) with the two-character string "00". value = "".join("00" if ord(c) > 0xFFFF else c for c in value) # 8. If input's code point length is greater than 128, truncate input, leaving only # the first 128 characters. if len(value) > 128: value = value[:128] # 9. If the first character in input is a U+0023 NUMBER SIGN character (#), remove # it. if value.startswith("#"): value = value[1:] # 10. Replace any character in input that is not an ASCII hex digit with the # character U+0030 DIGIT ZERO (0). value = "".join(c if c in string.hexdigits else "0" for c in value) # 11. While input's code point length is zero or not a multiple of three, append a # U+0030 DIGIT ZERO (0) character to input. while (len(value) == 0) or (len(value) % 3 != 0): value += "0" # 12. Split input into three strings of equal code point length, to obtain three # components. Let length be the code point length that all of those components # have (one third the code point length of input). length = int(len(value) / 3) red = value[:length] green = value[length : length * 2] blue = value[length * 2 :] # 13. If length is greater than 8, then remove the leading length-8 characters in # each component, and let length be 8. if length > 8: red, green, blue = (red[length - 8 :], green[length - 8 :], blue[length - 8 :]) length = 8 # 14. While length is greater than two and the first character in each component is # a U+0030 DIGIT ZERO (0) character, remove that character and reduce length by # one. while (length > 2) and (red[0] == "0" and green[0] == "0" and blue[0] == "0"): red, green, blue = (red[1:], green[1:], blue[1:]) length -= 1 # 15. If length is still greater than two, truncate each component, leaving only the # first two characters in each. if length > 2: red, green, blue = (red[:2], green[:2], blue[:2]) # 16. Let result be a simple color. # # 17. Interpret the first component as a hexadecimal number; let the red component # of result be the resulting number. # # 18. Interpret the second component as a hexadecimal number; let the green # component of result be the resulting number. # # 19. Interpret the third component as a hexadecimal number; let the blue component # of result be the resulting number. # # 20. Return result. return HTML5SimpleColor(int(red, 16), int(green, 16), int(blue, 16)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1717652670.6280024 webcolors-25.10.0/src/webcolors/_normalization.py0000644000000000000000000000766014630246277017015 0ustar00""" Normalization utilities for color values. """ # SPDX-License-Identifier: BSD-3-Clause from ._definitions import _HEX_COLOR_RE from ._types import IntegerRGB, IntTuple, PercentRGB, PercentTuple def normalize_hex(hex_value: str) -> str: """ Normalize a hexadecimal color value to a string consisting of the character `#` followed by six lowercase hexadecimal digits (what HTML5 terms a "valid lowercase simple color"). If the supplied value cannot be interpreted as a hexadecimal color value, :exc:`ValueError` is raised. See :ref:`the conventions used by this module ` for information on acceptable formats for hexadecimal values. Examples: .. doctest:: >>> normalize_hex("#0099cc") '#0099cc' >>> normalize_hex("#0099CC") '#0099cc' >>> normalize_hex("#09c") '#0099cc' >>> normalize_hex("#09C") '#0099cc' >>> normalize_hex("#0099gg") Traceback (most recent call last): ... ValueError: '#0099gg' is not a valid hexadecimal color value. >>> normalize_hex("0099cc") Traceback (most recent call last): ... ValueError: '0099cc' is not a valid hexadecimal color value. :param hex_value: The hexadecimal color value to normalize. :raises ValueError: when the input is not a valid hexadecimal color value. """ if (match := _HEX_COLOR_RE.match(hex_value)) is None: raise ValueError(f'"{hex_value}" is not a valid hexadecimal color value.') hex_digits = match.group(1) if len(hex_digits) == 3: hex_digits = "".join(2 * s for s in hex_digits) return f"#{hex_digits.lower()}" def _normalize_integer_rgb(value: int) -> int: """ Internal normalization function for clipping integer values into the permitted range (0-255, inclusive). """ return 0 if value < 0 else 255 if value > 255 else value def normalize_integer_triplet(rgb_triplet: IntTuple) -> IntegerRGB: """ Normalize an integer ``rgb()`` triplet so that all values are within the range 0..255. Examples: .. doctest:: >>> normalize_integer_triplet((128, 128, 128)) IntegerRGB(red=128, green=128, blue=128) >>> normalize_integer_triplet((0, 0, 0)) IntegerRGB(red=0, green=0, blue=0) >>> normalize_integer_triplet((255, 255, 255)) IntegerRGB(red=255, green=255, blue=255) >>> normalize_integer_triplet((270, -20, -0)) IntegerRGB(red=255, green=0, blue=0) :param rgb_triplet: The percentage `rgb()` triplet to normalize. """ return IntegerRGB._make(_normalize_integer_rgb(value) for value in rgb_triplet) def _normalize_percent_rgb(value: str) -> str: """ Internal normalization function for clipping percent values into the permitted range (0%-100%, inclusive). """ value = value.split("%")[0] percent = float(value) if "." in value else int(value) return "0%" if percent < 0 else "100%" if percent > 100 else f"{percent}%" def normalize_percent_triplet(rgb_triplet: PercentTuple) -> PercentRGB: """ Normalize a percentage ``rgb()`` triplet so that all values are within the range 0%..100%. Examples: .. doctest:: >>> normalize_percent_triplet(("50%", "50%", "50%")) PercentRGB(red='50%', green='50%', blue='50%') >>> normalize_percent_triplet(("0%", "100%", "0%")) PercentRGB(red='0%', green='100%', blue='0%') >>> normalize_percent_triplet(("-10%", "-0%", "500%")) PercentRGB(red='0%', green='0%', blue='100%') :param rgb_triplet: The percentage `rgb()` triplet to normalize. """ return PercentRGB._make(_normalize_percent_rgb(value) for value in rgb_triplet) def _percent_to_integer(percent: str) -> int: """ Internal helper for converting a percentage value to an integer between 0 and 255 inclusive. """ return int(round(float(percent.split("%")[0]) / 100 * 255)) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716189356.8687346 webcolors-25.10.0/src/webcolors/_types.py0000644000000000000000000000350214622574255015263 0ustar00""" Types and type aliases used to represent colors in various formats. """ # SPDX-License-Identifier: BSD-3-Clause import typing class IntegerRGB(typing.NamedTuple): """ :class:`~typing.NamedTuple` representing an integer RGB triplet. Has three fields, each of type :class:`int` and in the range 0-255 inclusive: .. attribute:: red The red portion of the color value. .. attribute:: green The green portion of the color value. .. attribute:: blue The blue portion of the color value. """ red: int green: int blue: int class PercentRGB(typing.NamedTuple): """ :class:`~typing.NamedTuple` representing a percentage RGB triplet. Has three fields, each of type :class:`str` and representing a percentage value in the range 0%-100% inclusive: .. attribute:: red The red portion of the color value. .. attribute:: green The green portion of the color value. .. attribute:: blue The blue portion of the color value. """ red: str green: str blue: str class HTML5SimpleColor(typing.NamedTuple): """ :class:`~typing.NamedTuple` representing an HTML5 simple color. Has three fields, each of type :class:`int` and in the range 0-255 inclusive: .. attribute:: red The red portion of the color value. .. attribute:: green The green portion of the color value. .. attribute:: blue The blue portion of the color value. """ red: int green: int blue: int # Union type representing the possible types of an integer RGB tuple. IntTuple = typing.Union[IntegerRGB, HTML5SimpleColor, typing.Tuple[int, int, int]] # Union type representing the possible types of a percentage RGB tuple. PercentTuple = typing.Union[PercentRGB, typing.Tuple[str, str, str]] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1399112166.0 webcolors-25.10.0/tests/__init__.py0000644000000000000000000000000012331140746014046 0ustar00././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1717652401.746316 webcolors-25.10.0/tests/definitions.py0000644000000000000000000001155314630245662014650 0ustar00""" Test accuracy of the mappings of color names and values, by extracting the definitions of the colors from the relevant standards documents. """ # SPDX-License-Identifier: BSD-3-Clause # pylint: disable=protected-access import re import unittest import html5lib # noqa: F401 pylint: disable=unused-import import requests from bs4 import BeautifulSoup import webcolors class HTML4DefinitionTests(unittest.TestCase): """ Extract the names and values of the 16 defined HTML 4 colors from the online version of the standard, and check them against this module's definitions of them. """ def setUp(self): """ Fetch the HTML 4 color definitions and store them on self.html4_colors. """ self.html4_colors = {} soup = BeautifulSoup( requests.get("http://www.w3.org/TR/html401/types.html", timeout=5).content, "html.parser", ) color_table = soup.find( "table", attrs={"summary": "Table of color names and their sRGB values"} ) for cell in color_table.findAll("td"): if "width" not in cell.attrs: color_name, color_value = cell.text.split(" = ") self.html4_colors[color_name] = color_value.replace('"', "").strip() def test_color_definitions(self): """ Ensure the values in this module match those in the HTML 4 specification. """ for color_name, color_value in self.html4_colors.items(): extracted = webcolors._definitions._HTML4_NAMES_TO_HEX[color_name.lower()] assert color_value.lower() == extracted class CSS21DefinitionTests(unittest.TestCase): """ Extract the names and values of the 17 defined CSS 2.1 colors from the online version of the standard, and check them against this module's definitions of them. """ def setUp(self): """ Fetch the CSS2.1 color definitions and store them on self.css21_colors. """ self.color_matching_re = re.compile(r"^([a-z]+) (#[a-fA-F0-9]{6})$") self.css21_colors = {} soup = BeautifulSoup( requests.get("http://www.w3.org/TR/CSS2/syndata.html", timeout=5).content, "html.parser", ) color_table = soup.find("div", attrs={"id": "TanteksColorDiagram20020613"}) for color_square in color_table.findAll("span", attrs={"class": "colorsquare"}): color_name, color_value = self.color_matching_re.search( color_square.text ).groups() self.css21_colors[color_name] = color_value def test_color_definitions(self): """ Ensure thie values in this module match those in the CSS2.1 specification. """ for color_name, color_value in self.css21_colors.items(): extracted = webcolors._definitions._CSS21_NAMES_TO_HEX[color_name.lower()] assert color_value.lower() == extracted class CSS3DefinitionTests(unittest.TestCase): """ Extract the names and values of the 147 defined CSS 3 colors from the online version of the standard, and check them against this module's definitions of them. """ def setUp(self): """ Fetch the CSS3 color definitions and store them on self.css3_colors. """ self.css3_colors = {} soup = BeautifulSoup( requests.get("http://www.w3.org/TR/css3-color/", timeout=5).content, "html5lib", ) color_table = soup.findAll("table", attrs={"class": "colortable"})[1] color_names = [dfn.text for dfn in color_table.findAll("dfn")] hex_values = [ td.text.strip() for td in color_table.findAll( "td", attrs={"class": "c", "style": "background:silver"} ) if td.text.startswith("#") ] rgb_values = [ td.text.strip() for td in color_table.findAll( "td", attrs={"class": "c", "style": "background:silver"} ) if not td.text.startswith("#") and not td.text.startswith("&") and td.text.strip() ] for i, color_name in enumerate(color_names): self.css3_colors[color_name] = { "hex": hex_values[i], "rgb": tuple(map(int, rgb_values[i].split(","))), } def test_color_definitions(self): """ Ensure the values in this module match those in the CSS3 specification. """ for color_name, color_values in self.css3_colors.items(): extracted_hex = webcolors._definitions._CSS3_NAMES_TO_HEX[ color_name.lower() ] extracted_rgb = webcolors.name_to_rgb(color_name) assert color_values["hex"].lower() == extracted_hex assert color_values["rgb"] == extracted_rgb if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1716189615.5951812 webcolors-25.10.0/tests/full_colors.py0000644000000000000000000000564614622574660014672 0ustar00""" Test case which exercises webcolors' conversion functions across all 16,777,216 possible hexadecimal values and all 16,777,216 possible integer rgb() triplet values. You should not ever need to run this test; it is not part of the normal unit-test suite, and is used only as a final check when preparing a new release of webcolors. Because it generates each of the nearly 17 million color values multiple times, this test case takes some time to run and consumes most or all available CPU while running. As a consolation, it is somewhat efficient with respect to memory. Due to the inherent imprecision of floating-point percentage values, and the fact that the legal (with respect to the CSS standards) set of percentage rgb() triplets is uncountably infinite, percentage rgb() triplets are not exhaustively tested here, and the normal test suite is used to ensure correctness of the conversion functions for those values. The only test performed here for percentage rgb() triplets is to ensure that converting an integer rgb() triplet to percentage and back returns the original integer values, for consistency. """ # SPDX-License-Identifier: BSD-3-Clause import unittest import webcolors def hex_colors(): """ Generator which yields all 2**24 hexadecimal color values, in order starting from #000000. """ for i in range(16777217): yield f"#{i:06x}" def int_colors(): """ Generator which yields all 2**24 integer rgb() triplets, in order starting from (0,0,0). """ red_counter = tuple(range(256)) green_counter = tuple(range(256)) blue_counter = tuple(range(256)) for red_value in red_counter: for green_value in green_counter: for blue_value in blue_counter: yield (red_value, green_value, blue_value) class FullColorTest(unittest.TestCase): """ Exercise color conversion on all testable values. """ def test_full_colors(self): """ Test conversion between hexadecimal and integer rgb() for all 2**24 possible values. """ for hex_color, int_triplet in zip(hex_colors(), int_colors()): assert int_triplet == webcolors.hex_to_rgb(hex_color) assert hex_color == webcolors.rgb_to_hex(int_triplet) def test_triplet_conversion(self): """ Test conversion between integer and percentage rgb() as fully as possible. Because exhaustive testing is not possible (the set of valid percentgae rgb() triplets is uncountably infinite), this test ensures that for each of the 2**24 possible integer triplets, conversion to percentage and back gives the starting value. """ for int_triplet in int_colors(): conversion = webcolors.rgb_percent_to_rgb( webcolors.rgb_to_rgb_percent(int_triplet) ) assert int_triplet == conversion if __name__ == "__main__": unittest.main() ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761809376.1977985 webcolors-25.10.0/tests/test_conformance.py0000644000000000000000000001615115100611740015650 0ustar00""" Conformance tests which do not require an internet connection or HTML parsing libraries, and so can be run as part of the normal test suite of webcolors. For tests which extract the relevant values, during the test run, from the online standards documents (and so require both an internet connection and an HTML parsing library), see the file ``definitions.py`` in this directory. """ # SPDX-License-Identifier: BSD-3-Clause import pytest import webcolors # The mappings of color names to values below are used for conformance testing; while # the main webcolors module makes use of alphabetized, normalized mappings to hex # values, the mappings below are the definitions in precisely the form they take in the # relevant standards documents (they were produced via automated extraction from the # HTML of those documents, to avoid the possibility of human copy/paste error). # # Sources are: # # HTML 4 colors: http://www.w3.org/TR/html401/types.html#h-6.5 # # SVG colors (which CSS 3 adopted): # http://www.w3.org/TR/SVG/types.html#ColorKeywords # # Conformance of this module with the relevant standards is proven by comparing its # output to these mappings. HTML4_COLOR_DEFINITIONS = { "Black": "#000000", "Silver": "#C0C0C0", "Gray": "#808080", "White": "#FFFFFF", "Maroon": "#800000", "Red": "#FF0000", "Purple": "#800080", "Fuchsia": "#FF00FF", "Green": "#008000", "Lime": "#00FF00", "Olive": "#808000", "Yellow": "#FFFF00", "Navy": "#000080", "Blue": "#0000FF", "Teal": "#008080", "Aqua": "#00FFFF", } SVG_COLOR_DEFINITIONS = { "aliceblue": (240, 248, 255), "antiquewhite": (250, 235, 215), "aqua": (0, 255, 255), "aquamarine": (127, 255, 212), "azure": (240, 255, 255), "beige": (245, 245, 220), "bisque": (255, 228, 196), "black": (0, 0, 0), "blanchedalmond": (255, 235, 205), "blue": (0, 0, 255), "blueviolet": (138, 43, 226), "brown": (165, 42, 42), "burlywood": (222, 184, 135), "cadetblue": (95, 158, 160), "chartreuse": (127, 255, 0), "chocolate": (210, 105, 30), "coral": (255, 127, 80), "cornflowerblue": (100, 149, 237), "cornsilk": (255, 248, 220), "crimson": (220, 20, 60), "cyan": (0, 255, 255), "darkblue": (0, 0, 139), "darkcyan": (0, 139, 139), "darkgoldenrod": (184, 134, 11), "darkgray": (169, 169, 169), "darkgreen": (0, 100, 0), "darkgrey": (169, 169, 169), "darkkhaki": (189, 183, 107), "darkmagenta": (139, 0, 139), "darkolivegreen": (85, 107, 47), "darkorange": (255, 140, 0), "darkorchid": (153, 50, 204), "darkred": (139, 0, 0), "darksalmon": (233, 150, 122), "darkseagreen": (143, 188, 143), "darkslateblue": (72, 61, 139), "darkslategray": (47, 79, 79), "darkslategrey": (47, 79, 79), "darkturquoise": (0, 206, 209), "darkviolet": (148, 0, 211), "deeppink": (255, 20, 147), "deepskyblue": (0, 191, 255), "dimgray": (105, 105, 105), "dimgrey": (105, 105, 105), "dodgerblue": (30, 144, 255), "firebrick": (178, 34, 34), "floralwhite": (255, 250, 240), "forestgreen": (34, 139, 34), "fuchsia": (255, 0, 255), "gainsboro": (220, 220, 220), "ghostwhite": (248, 248, 255), "gold": (255, 215, 0), "goldenrod": (218, 165, 32), "gray": (128, 128, 128), "grey": (128, 128, 128), "green": (0, 128, 0), "greenyellow": (173, 255, 47), "honeydew": (240, 255, 240), "hotpink": (255, 105, 180), "indianred": (205, 92, 92), "indigo": (75, 0, 130), "ivory": (255, 255, 240), "khaki": (240, 230, 140), "lavender": (230, 230, 250), "lavenderblush": (255, 240, 245), "lawngreen": (124, 252, 0), "lemonchiffon": (255, 250, 205), "lightblue": (173, 216, 230), "lightcoral": (240, 128, 128), "lightcyan": (224, 255, 255), "lightgoldenrodyellow": (250, 250, 210), "lightgray": (211, 211, 211), "lightgreen": (144, 238, 144), "lightgrey": (211, 211, 211), "lightpink": (255, 182, 193), "lightsalmon": (255, 160, 122), "lightseagreen": (32, 178, 170), "lightskyblue": (135, 206, 250), "lightslategray": (119, 136, 153), "lightslategrey": (119, 136, 153), "lightsteelblue": (176, 196, 222), "lightyellow": (255, 255, 224), "lime": (0, 255, 0), "limegreen": (50, 205, 50), "linen": (250, 240, 230), "magenta": (255, 0, 255), "maroon": (128, 0, 0), "mediumaquamarine": (102, 205, 170), "mediumblue": (0, 0, 205), "mediumorchid": (186, 85, 211), "mediumpurple": (147, 112, 219), "mediumseagreen": (60, 179, 113), "mediumslateblue": (123, 104, 238), "mediumspringgreen": (0, 250, 154), "mediumturquoise": (72, 209, 204), "mediumvioletred": (199, 21, 133), "midnightblue": (25, 25, 112), "mintcream": (245, 255, 250), "mistyrose": (255, 228, 225), "moccasin": (255, 228, 181), "navajowhite": (255, 222, 173), "navy": (0, 0, 128), "oldlace": (253, 245, 230), "olive": (128, 128, 0), "olivedrab": (107, 142, 35), "orange": (255, 165, 0), "orangered": (255, 69, 0), "orchid": (218, 112, 214), "palegoldenrod": (238, 232, 170), "palegreen": (152, 251, 152), "paleturquoise": (175, 238, 238), "palevioletred": (219, 112, 147), "papayawhip": (255, 239, 213), "peachpuff": (255, 218, 185), "peru": (205, 133, 63), "pink": (255, 192, 203), "plum": (221, 160, 221), "powderblue": (176, 224, 230), "purple": (128, 0, 128), "red": (255, 0, 0), "rosybrown": (188, 143, 143), "royalblue": (65, 105, 225), "saddlebrown": (139, 69, 19), "salmon": (250, 128, 114), "sandybrown": (244, 164, 96), "seagreen": (46, 139, 87), "seashell": (255, 245, 238), "sienna": (160, 82, 45), "silver": (192, 192, 192), "skyblue": (135, 206, 235), "slateblue": (106, 90, 205), "slategray": (112, 128, 144), "slategrey": (112, 128, 144), "snow": (255, 250, 250), "springgreen": (0, 255, 127), "steelblue": (70, 130, 180), "tan": (210, 180, 140), "teal": (0, 128, 128), "thistle": (216, 191, 216), "tomato": (255, 99, 71), "turquoise": (64, 224, 208), "violet": (238, 130, 238), "wheat": (245, 222, 179), "white": (255, 255, 255), "whitesmoke": (245, 245, 245), "yellow": (255, 255, 0), "yellowgreen": (154, 205, 50), } @pytest.mark.parametrize( ["color", "hex_value"], HTML4_COLOR_DEFINITIONS.items(), ids=HTML4_COLOR_DEFINITIONS.keys(), ) def test_html4_definition_conformance(color: str, hex_value: str): """ Compare the results of name-to-hex conversion to the canonical hex values provided in the HTML 4 specification. """ assert webcolors.normalize_hex(hex_value) == webcolors.name_to_hex(color) @pytest.mark.parametrize( ["color", "triplet"], SVG_COLOR_DEFINITIONS.items(), ids=SVG_COLOR_DEFINITIONS.keys(), ) def test_svg_definition_conformance(color: str, triplet: webcolors.IntTuple): """ Compare the results of name-to-rgb-triplet conversion to the canonical triplet values provided in the SVG specification. """ assert triplet == webcolors.name_to_rgb(color) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761895679.6005015 webcolors-25.10.0/tests/test_conversion.py0000644000000000000000000003405515101062400015540 0ustar00""" Test the color-format conversion utilities. """ # SPDX-License-Identifier: BSD-3-Clause # pylint: disable=protected-access import pytest import webcolors @pytest.mark.parametrize( ["hex_value", "name"], [ ("#ffffff", "white"), ("#fff", "white"), ("#000080", "navy"), ("#daa520", "goldenrod"), ], ids=["white-six-digit", "white-three-digit", "navy", "goldenrod"], ) def test_hex_to_name(hex_value: str, name: str): """ Conversion from hex to color name succeeds. """ assert name == webcolors.hex_to_name(hex_value) def test_hex_to_name_unnamed_in_any(): """ A hex code which does not correspond to a named color raises ValueError. """ with pytest.raises(ValueError, match="has no defined color name"): webcolors.hex_to_name("#123456") def test_hex_to_name_unnamed_in_spec(): """ A hex code which does not correspond to a named color in the given specification raises ValueError. """ # This is 'goldenrod' in CSS 3 list, unnamed in HTML 4. with pytest.raises(ValueError, match="has no defined color name"): webcolors.hex_to_name("#daa520", spec=webcolors.HTML4) @pytest.mark.parametrize("spec", webcolors._definitions._SUPPORTED_SPECIFICATIONS) def test_hex_to_name_supported_spec(spec: str): """ Explicitly using one of the supported specifications succeeds. """ assert "white" == webcolors.hex_to_name("#ffffff", spec=spec) @pytest.mark.parametrize("spec", ["css1", "css4", "html5"]) def test_hex_to_name_unspported_spec(spec: str): """ Using an unsupported specification raises ValueError. """ with pytest.raises(ValueError, match="not a supported specification"): webcolors.hex_to_name("#ffffff", spec=spec) @pytest.mark.parametrize( ["hex_value", "triplet"], [("#fff", (255, 255, 255)), ("#ffffff", (255, 255, 255)), ("#000080", (0, 0, 128))], ids=["white-three-digit", "white-six-digit", "navy"], ) def test_hex_to_rgb(hex_value: str, triplet: webcolors.IntTuple): """ Conversion from hex to integer RGB triplet succeeds. """ result = webcolors.hex_to_rgb(hex_value) assert isinstance(result, webcolors.IntegerRGB) assert triplet == result @pytest.mark.parametrize( ["hex_value", "triplet"], [ ("#fff", ("100%", "100%", "100%")), ("#ffffff", ("100%", "100%", "100%")), ("#000080", ("0%", "0%", "50%")), ], ids=["white-three-digit", "white-six-digit", "navy"], ) def test_hex_to_rgb_percent(hex_value: str, triplet: webcolors.PercentTuple): """ Conversion from hex to percent RGB triplet succeeds. """ result = webcolors.hex_to_rgb_percent(hex_value) assert isinstance(result, webcolors.PercentRGB) assert triplet == result @pytest.mark.parametrize( ["triplet", "name"], [ ((255, 255, 255), "white"), ((0, 0, 128), "navy"), ((218, 165, 32), "goldenrod"), (webcolors.IntegerRGB(218, 165, 32), "goldenrod"), ], ids=["white", "navy", "goldenrod", "goldenrod-integerrgb"], ) def test_rgb_to_name(triplet: webcolors.IntTuple, name: str): """ Conversion from integer RGB triplet to color name succeeds. """ assert name == webcolors.rgb_to_name(triplet) def test_rgb_to_name_unnamed_in_any(): """ An integer RGB triplet which does not correspond to a named color raises ValueError. """ with pytest.raises(ValueError, match="has no defined color name"): webcolors.rgb_to_name((18, 52, 86)) def test_rgb_to_name_unnamed_in_spec(): """ An integer RGB triplet which does not correspond to a named color in the given specification raises ValueError. """ # This is 'goldenrod' in CSS 3 list, unnamed in HTML 4. with pytest.raises(ValueError, match="has no defined color name"): webcolors.rgb_to_name((218, 165, 32), spec=webcolors.HTML4) @pytest.mark.parametrize("spec", webcolors._definitions._SUPPORTED_SPECIFICATIONS) def test_rgb_to_name_supported_spec(spec: str): """ Using one of the supported specifications succeeds. """ assert "white" == webcolors.rgb_to_name((255, 255, 255), spec=spec) @pytest.mark.parametrize("spec", ["css1", "css4", "html5"]) def test_rgb_to_name_unsupported_spec(spec: str): """ Using an unsupported specification raises ValueError. """ with pytest.raises(ValueError, match="not a supported specification"): webcolors.rgb_to_name((255, 255, 255), spec=spec) @pytest.mark.parametrize( ["triplet", "hex_value"], [ ((255, 255, 255), "#ffffff"), ((0, 0, 128), "#000080"), ((218, 165, 32), "#daa520"), ], ids=["white", "navy", "goldenrod"], ) def test_rgb_to_hex(triplet: webcolors.IntTuple, hex_value: str): """ Conversion from integer RGB triplet to hex succeeds. """ assert hex_value == webcolors.rgb_to_hex(triplet) @pytest.mark.parametrize( ["triplet", "percent_triplet"], [ ((255, 255, 255), ("100%", "100%", "100%")), ((0, 0, 128), ("0%", "0%", "50%")), ((218, 165, 32), ("85.49%", "64.71%", "12.5%")), ], ids=["white", "navy", "goldenrod"], ) def test_rgb_to_rgb_percent( triplet: webcolors.IntTuple, percent_triplet: webcolors.PercentTuple ): """ Conversion from integer RGB triplet to percent RGB triplet succeeds. """ result = webcolors.rgb_to_rgb_percent(triplet) assert isinstance(result, webcolors.PercentRGB) assert percent_triplet == result @pytest.mark.parametrize( ["name", "hex_value"], [("white", "#ffffff"), ("navy", "#000080"), ("goldenrod", "#daa520")], ids=["white", "navy", "goldenrod"], ) def test_name_to_hex(name: str, hex_value: str): """ Test correct conversion of color names to hex. """ assert hex_value == webcolors.name_to_hex(name) @pytest.mark.parametrize( ["name", "spec"], [("goldenrod", "html4"), ("glue", "css21"), ("breen", "css3")] ) def test_name_to_hex_unnamed(name: str, spec: str): """ A name which does not correspond to a color, or does not correspond to a color in the given specification, raises ValueError. """ with pytest.raises(ValueError, match="is not defined as a named color"): webcolors.name_to_hex(name, spec=spec) @pytest.mark.parametrize("spec", webcolors._definitions._SUPPORTED_SPECIFICATIONS) def test_name_to_hex_supported_spec(spec: str): """ Using one of the supported specifications succeeds. """ assert "#ffffff" == webcolors.name_to_hex("white", spec=spec) @pytest.mark.parametrize("spec", ["css1", "css4", "html5"]) def test_name_to_hex_usupported_spec(spec: str): """ Using an unsupported specification raises ValueError. """ with pytest.raises(ValueError, match="not a supported specification"): webcolors.name_to_hex("white", spec=spec) @pytest.mark.parametrize( ["name", "triplet"], [("white", (255, 255, 255)), ("navy", (0, 0, 128)), ("goldenrod", (218, 165, 32))], ids=["white", "navy", "goldenrod"], ) def test_name_to_rgb(name: str, triplet: webcolors.IntTuple): """ Conversion from color name to integer RGB triplet succeeds. """ result = webcolors.name_to_rgb(name) assert isinstance(result, webcolors.IntegerRGB) assert triplet == result @pytest.mark.parametrize( ["name", "triplet"], [ ("white", ("100%", "100%", "100%")), ("navy", ("0%", "0%", "50%")), ("goldenrod", ("85.49%", "64.71%", "12.5%")), ], ids=["white", "navy", "goldenrod"], ) def test_name_to_rgb_percent(name: str, triplet: webcolors.PercentTuple): """ Conversion from color name to percent RGB triplet succeeds. """ result = webcolors.name_to_rgb_percent(name) assert isinstance(result, webcolors.PercentRGB) assert triplet == result @pytest.mark.parametrize( ["triplet", "name"], [ (("100%", "100%", "100%"), "white"), (("0%", "0%", "50%"), "navy"), (("85.49%", "64.71%", "12.5%"), "goldenrod"), (webcolors.PercentRGB("85.49%", "64.71%", "12.5%"), "goldenrod"), ], ids=["white", "navy", "goldenrod", "goldenrod-percentrgb"], ) def test_rgb_percent_to_name(triplet: webcolors.PercentTuple, name: str): """ Conversion from percent RGB triplet to color name succeeds. """ assert name == webcolors.rgb_percent_to_name(triplet) def test_rgb_percent_to_name_unnamed_in_any(): """ A percent RGB triplet which does not correspond to a named color raises ValueError. """ with pytest.raises(ValueError, match="has no defined color name"): webcolors.rgb_percent_to_name(("7.06%", "20.39%", "33.73%")) def test_rgb_percent_to_name_unnamed_in_spec(): """ A percent RGB triplet which does not correspond to a named color in the given specification raises ValueError. """ # This is 'goldenrod' in CSS 3 list, unnamed in HTML 4. with pytest.raises(ValueError, match="has no defined color name"): webcolors.rgb_percent_to_name( ("85.49%", "64.71%", "12.5%"), spec=webcolors.HTML4 ) @pytest.mark.parametrize("spec", webcolors._definitions._SUPPORTED_SPECIFICATIONS) def test_rgb_percent_to_name_supported_spec(spec: str): """ Using one of the supported specifications succeeds. """ assert "white" == webcolors.rgb_percent_to_name(("100%", "100%", "100%"), spec=spec) @pytest.mark.parametrize("spec", ["css1", "css4", "html5"]) def test_rgb_percent_to_name_unsupported_spec(spec: str): """ Using an unsupported specification raises ValueError. """ with pytest.raises(ValueError, match="not a supported specification"): webcolors.rgb_percent_to_name(("100%", "100%", "100%"), spec=spec) @pytest.mark.parametrize( ["triplet", "hex_value"], [ (("100%", "100%", "0%"), "#ffff00"), (("0%", "0%", "50%"), "#000080"), (("85.49%", "64.71%", "12.5%"), "#daa520"), ], ids=["white", "navy", "goldenrod"], ) def test_rgb_percent_to_hex(triplet: webcolors.PercentTuple, hex_value: str): """ Conversion from percent RGB triplet to hex succeeds. """ assert hex_value == webcolors.rgb_percent_to_hex(triplet) @pytest.mark.parametrize( ["triplet", "int_triplet"], [ (("100%", "100%", "0%"), (255, 255, 0)), (("0%", "0%", "50%"), (0, 0, 128)), (("85.49%", "64.71%", "12.5%"), (218, 165, 32)), ], ) def test_rgb_percent_to_rgb( triplet: webcolors.PercentTuple, int_triplet: webcolors.IntTuple ): """ Conversion from percent RGB triplet to integer RGB triplet succeeds. """ result = webcolors.rgb_percent_to_rgb(triplet) assert isinstance(result, webcolors.IntegerRGB) assert int_triplet == result @pytest.mark.parametrize( ["value", "name"], [ ("#a9a9a9", "darkgray"), ("#2f4f4f", "darkslategray"), ("#696969", "dimgray"), ("#808080", "gray"), ("#d3d3d3", "lightgray"), ("#778899", "lightslategray"), ("#708090", "slategray"), ], ids=[ "darkgray", "darkslategray", "dimgray", "gray", "lightgray", "lightslategray", "slategray", ], ) def test_spelling_variants_hex(value: str, name: str): """ When asked to name a hex color value that maps to either of 'gray' or 'grey' in CSS3, or a related color like 'darkgray'/'darkgrey', webcolors always picks 'gray' as the spelling. """ assert name == webcolors.hex_to_name(value, spec=webcolors.CSS3) @pytest.mark.parametrize( ["value", "name"], [ ((169, 169, 169), "darkgray"), ((47, 79, 79), "darkslategray"), ((105, 105, 105), "dimgray"), ((128, 128, 128), "gray"), ((211, 211, 211), "lightgray"), ((119, 136, 153), "lightslategray"), ((112, 128, 144), "slategray"), ], ids=[ "darkgray", "darkslategray", "dimgray", "gray", "lightgray", "lightslategray", "slategray", ], ) def test_spelling_variants_int(value: webcolors.IntTuple, name: str): """ When asked to name an integer RGB color value that maps to either of 'gray' or 'grey' in CSS3, or a related color like 'darkgray'/'darkgrey', webcolors always picks 'gray' as the spelling. """ assert name == webcolors.rgb_to_name(value, spec=webcolors.CSS3) @pytest.mark.parametrize( ["value", "name"], [ (("66.27%", "66.27%", "66.27%"), "darkgray"), (("18.43%", "30.98%", "30.98%"), "darkslategray"), (("41.18%", "41.18%", "41.18%"), "dimgray"), (("50%", "50%", "50%"), "gray"), (("82.75%", "82.75%", "82.75%"), "lightgray"), (("46.67%", "53.33%", "60.00%"), "lightslategray"), (("43.92%", "50%", "56.47%"), "slategray"), ], ids=[ "darkgray", "darkslategray", "dimgray", "gray", "lightgray", "lightslategray", "slategray", ], ) def test_spelling_variants_percent(value: webcolors.PercentTuple, name: str): """ When asked to name a percent RGB color value that maps to either of 'gray' or 'grey' in CSS3, or a related color like 'darkgray'/'darkgrey', webcolors always picks 'gray' as the spelling. """ assert name == webcolors.rgb_percent_to_name(value, spec=webcolors.CSS3) @pytest.mark.parametrize( ["spec", "mapping"], [ (webcolors.HTML4, webcolors._definitions._HTML4_NAMES_TO_HEX), (webcolors.CSS2, webcolors._definitions._CSS2_NAMES_TO_HEX), (webcolors.CSS21, webcolors._definitions._CSS21_NAMES_TO_HEX), (webcolors.CSS3, webcolors._definitions._CSS3_NAMES_TO_HEX), ], ids=["html4", "css2", "css21", "css3"], ) def test_names_valid(spec: str, mapping: dict): """ names() correctly returns the set of names for a given spec. """ assert webcolors.names(spec) == list(sorted(mapping.keys())) @pytest.mark.parametrize("spec", ["CSS0", "HTML12", "random"]) def test_names_invalid(spec: str): """ names() raises ValueError when asked for an unsupported spec. """ with pytest.raises(ValueError, match="not a supported specification"): webcolors.names(spec) ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1761895845.321131 webcolors-25.10.0/tests/test_html5.py0000644000000000000000000001075115101062645014414 0ustar00""" Tests for the HTML5 color algorithms. """ # SPDX-License-Identifier: BSD-3-Claus # pylint: disable=protected-access import re import pytest import webcolors @pytest.mark.parametrize( ["raw", "parsed"], [ ("#ffffff", (255, 255, 255)), ("#000080", (0, 0, 128)), ("#daa520", (218, 165, 32)), ], ids=["white", "navy", "goldenrod"], ) def test_parse_simple_color(raw: str, parsed: webcolors.IntTuple): """ Test implementation of the HTML5 simple color parsing algorithm. """ result = webcolors.html5_parse_simple_color(raw) assert isinstance(result, webcolors.HTML5SimpleColor) assert parsed == result @pytest.mark.parametrize( ["value", "match"], [ ( "0099ccc", re.escape( "An HTML5 simple color must begin with the character '#' (U+0023)" ), ), ( "#09c", "An HTML5 simple color must be a Unicode string seven characters long", ), ( "#0000", "An HTML5 simple color must be a Unicode string seven characters long", ), ( "#0000000", "An HTML5 simple color must be a Unicode string seven characters long", ), ("#0000gg", "An HTML5 simple color must contain exactly six ASCII hex digits"), ( "#000000".encode("ascii"), "An HTML5 simple color must be a Unicode string seven characters long", ), ], ids=[ "too-long-no-hash", "three-digit", "four-digit", "too-long", "not-hex", "not-unicode", ], ) def test_parse_simple_color_error(value: str, match: str): """ Test error conditions of the HTML5 simple color parsing algorithm. """ with pytest.raises(ValueError, match=match): webcolors.html5_parse_simple_color(value) @pytest.mark.parametrize( ["raw", "serialized"], [ ((0, 0, 0), "#000000"), ((0, 0, 128), "#000080"), ((218, 165, 32), "#daa520"), (webcolors.IntegerRGB(218, 165, 32), "#daa520"), (webcolors.HTML5SimpleColor(218, 165, 32), "#daa520"), ], ids=["black", "navy", "goldenrod", "goldenrod-integerrgb", "goldenrod-html5tuple"], ) def test_serialize_simple_color(raw: webcolors.IntTuple, serialized: str): """ Test implementation of the HTML5 simple color serialization algorithm. """ assert serialized == webcolors.html5_serialize_simple_color(raw) @pytest.mark.parametrize( ["raw", "parsed"], [ ("chucknorris", (192, 0, 0)), ("Window", (0, 13, 0)), ("RE|SXLuAse", (224, 0, 224)), ("+=@FnnWL!Yb}5Dk", (0, 0, 176)), ("A" * 129, (170, 170, 170)), ], ids=["chucknorris", "system-color", "junk1", "junk2", "junk3"], ) def test_parse_legacy_color(raw: str, parsed: webcolors.IntTuple): """ Test implementation of the HTML5 legacy color parsing algorithm. """ result = webcolors.html5_parse_legacy_color(raw) assert isinstance(result, webcolors.HTML5SimpleColor) assert parsed == result @pytest.mark.parametrize("name", webcolors._definitions._CSS3_NAMES_TO_HEX) def test_parse_legacy_color_names(name: str): """ Test the HTML5 legacy color parsing of SVG/CSS3 color names. """ assert webcolors.html5_parse_legacy_color(name) == webcolors.name_to_rgb(name) @pytest.mark.parametrize( "value", ["#000", "#000000", "#fff", "#ffffff", "#000080"], ids=[ "three-digit-black", "six-digit-black", "three-digit-white", "six-digit-white", "navy", ], ) def test_parse_legacy_color_hex(value: str): """ Test the HTML5 legacy color parsing of three- and six-digit hexadecimal color values. """ assert webcolors.html5_parse_legacy_color(value) == webcolors.hex_to_rgb(value) @pytest.mark.parametrize( ["value", "match"], [ ( "#000000".encode("ascii"), "HTML5 legacy color parsing requires a Unicode string as input.", ), ("transparent", 'HTML5 legacy color parsing forbids "transparent" as a value.'), ("", "HTML5 legacy color parsing forbids empty string as a value."), ], ids=["non-unicode", "transparent", "empty"], ) def test_parse_legacy_color_error(value: str, match: str): """ Test error conditions of the HTML5 legacy color parsing algorithm. """ with pytest.raises(ValueError, match=match): webcolors.html5_parse_legacy_color(value) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1761895348.7564845 webcolors-25.10.0/tests/test_normalization.py0000644000000000000000000000662115101061665016253 0ustar00""" Test the color-value normalization functions. """ # SPDX-License-Identifier: BSD-3-Clause import pytest import webcolors @pytest.mark.parametrize( ["raw", "normalized"], [ ("#0099cc", "#0099cc"), ("#0099CC", "#0099cc"), ("#09c", "#0099cc"), ("#09C", "#0099cc"), ], ids=["lowercase", "uppercase", "three-digit-lowercase", "three-digit-uppercase"], ) def test_normalize_hex(raw: str, normalized: str): """ Hexadecimal normalization normalizes valid hex color codes to 6 digits, lowercase. """ assert normalized == webcolors.normalize_hex(raw) @pytest.mark.parametrize( "hex_value", ["0099cc", "#0000gg", "#0000", "#00000000"], ids=["no-hash", "not-hex", "too-short", "too-long"], ) def test_normalize_hex_format(hex_value: str): """ Hex normalization raises ValueError on invalid hex color code. """ with pytest.raises(ValueError, match="not a valid hexadecimal color value"): webcolors.normalize_hex(hex_value) @pytest.mark.parametrize( ["raw", "normalized"], [(255, 255), (0, 0), (128, 128), (-20, 0), (270, 255), (-0, 0)], ids=["max", "min", "middle", "clipped-to-min", "clipped-to-max", "negative-zero"], ) def test_normalize_integer_rgb(raw: int, normalized: int): """ Integer normalization clips to 0-255. """ # pylint: disable=protected-access assert normalized == webcolors._normalization._normalize_integer_rgb(raw) @pytest.mark.parametrize( ["triplet", "normalized"], [ ((128, 128, 128), (128, 128, 128)), ((0, 0, 0), (0, 0, 0)), ((255, 255, 255), (255, 255, 255)), ((270, -20, 128), (255, 0, 128)), ((-0, -0, -0), (0, 0, 0)), ], ids=["navy", "black", "white", "clipped", "negative-zero"], ) def test_normalize_integer_triplet( triplet: webcolors.IntTuple, normalized: webcolors.IntTuple ): """ Integer triplet normalization clips all values to 0-255. """ result = webcolors.normalize_integer_triplet(triplet) assert isinstance(result, webcolors.IntegerRGB) assert normalized == result @pytest.mark.parametrize( ["raw", "normalized"], [ ("0%", "0%"), ("100%", "100%"), ("62%", "62%"), ("-5%", "0%"), ("250%", "100%"), ("85.49%", "85.49%"), ("-0%", "0%"), ], ids=[ "min", "max", "not-special", "clipped-to-min", "clipped-to-max", "floating-point", "negative-zero", ], ) def test_normalize_percent_rgb(raw: str, normalized: str): """ Percent normalization clips to 0%-100%. """ # pylint: disable=protected-access assert normalized == webcolors._normalization._normalize_percent_rgb(raw) @pytest.mark.parametrize( ["triplet", "normalized"], [ (("50%", "50%", "50%"), ("50%", "50%", "50%")), (("0%", "100%", "0%"), ("0%", "100%", "0%")), (("-10%", "250%", "500%"), ("0%", "100%", "100%")), (("-0%", "-0%", "-0%"), ("0%", "0%", "0%")), ], ids=["gray", "green", "clipped", "negative-zero"], ) def test_normalize_percent_triplet( triplet: webcolors.PercentTuple, normalized: webcolors.PercentTuple ): """ Percent triplet normalization clips all values to 0%-100%. """ result = webcolors.normalize_percent_triplet(triplet) assert isinstance(result, webcolors.PercentRGB) assert normalized == result webcolors-25.10.0/PKG-INFO0000644000000000000000000000424100000000000011643 0ustar00Metadata-Version: 2.1 Name: webcolors Version: 25.10.0 Summary: A library for working with the color formats defined by HTML and CSS. Keywords: color,css,html,web Author: James Bennett License: BSD-3-Clause Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: Web Environment Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Programming Language :: Python :: 3.14 Classifier: Topic :: Utilities Project-URL: Documentation, https://webcolors.readthedocs.io Project-URL: Source Code, https://github.com/ubernostrum/webcolors Requires-Python: >=3.10 Description-Content-Type: text/x-rst .. -*-restructuredtext-*- .. image:: https://github.com/ubernostrum/webcolors/workflows/CI/badge.svg :alt: CI status image :target: https://github.com/ubernostrum/webcolors/actions?query=workflow%3ACI ``webcolors`` is a module for working with and converting between the various HTML/CSS color formats. Support is included for normalizing and converting between the following formats (RGB colorspace only; conversion to/from HSL can be handled by the ``colorsys`` module in the Python standard library): * Specification-defined color names * Six-digit hexadecimal * Three-digit hexadecimal * Integer ``rgb()`` triplet * Percentage ``rgb()`` triplet For example: .. code-block:: python >>> import webcolors >>> webcolors.hex_to_name("#daa520") 'goldenrod' Implementations are also provided for the HTML5 color parsing and serialization algorithms. For example, parsing the infamous "chucknorris" string into an ``rgb()`` triplet: .. code-block:: python >>> import webcolors >>> webcolors.html5_parse_legacy_color("chucknorris") HTML5SimpleColor(red=192, green=0, blue=0) Full documentation is `available online `_.