From e356b248380a7620b1a3aef680300009941821b9 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 13:18:12 -0400 Subject: [PATCH 001/119] python: init package with BrandMeta --- examples/brand-meta-full.yml | 11 + examples/brand-meta-small.yml | 3 + pkg-py/README.md | 14 + pkg-py/README.qmd | 16 + pkg-py/pyproject.toml | 25 + pkg-py/src/brand_yaml/__init__.py | 35 ++ pkg-py/src/brand_yaml/_meta.py | 59 +++ pkg-py/tests/test_meta.py | 46 ++ pkg-py/tests/utils/__init__.py | 5 + pkg-py/uv.lock | 820 ++++++++++++++++++++++++++++++ 10 files changed, 1034 insertions(+) create mode 100644 examples/brand-meta-full.yml create mode 100644 examples/brand-meta-small.yml create mode 100644 pkg-py/README.md create mode 100644 pkg-py/README.qmd create mode 100644 pkg-py/pyproject.toml create mode 100644 pkg-py/src/brand_yaml/__init__.py create mode 100644 pkg-py/src/brand_yaml/_meta.py create mode 100644 pkg-py/tests/test_meta.py create mode 100644 pkg-py/tests/utils/__init__.py create mode 100644 pkg-py/uv.lock diff --git a/examples/brand-meta-full.yml b/examples/brand-meta-full.yml new file mode 100644 index 00000000..808d9487 --- /dev/null +++ b/examples/brand-meta-full.yml @@ -0,0 +1,11 @@ +meta: + name: + full: Very Big Corporation of America + short: VBC + link: + home: https://very-big-corp.com + mastodon: https://mastodon.social/@VeryBigCorpOfficial + github: https://github.com/Very-Big-Corp + linkedin: https://linkedin.com/company/very-big-corp + twitter: https://twitter.com/VeryBigCorp + facebook: https://facebook.com/Very-Big-Corp diff --git a/examples/brand-meta-small.yml b/examples/brand-meta-small.yml new file mode 100644 index 00000000..9a391ca8 --- /dev/null +++ b/examples/brand-meta-small.yml @@ -0,0 +1,3 @@ +meta: + name: Very Big Corp. of America + link: https://very-big-corp.com diff --git a/pkg-py/README.md b/pkg-py/README.md new file mode 100644 index 00000000..00518642 --- /dev/null +++ b/pkg-py/README.md @@ -0,0 +1,14 @@ +# Brand YAML + + +``` python +from brand_yaml import Brand + +brand = Brand( + meta = {"name": "Posit PBC", "link": "https://posit.co"} +) + +brand.meta +``` + + BrandMeta(name='Posit PBC', link=Url('https://posit.co/')) diff --git a/pkg-py/README.qmd b/pkg-py/README.qmd new file mode 100644 index 00000000..5b646444 --- /dev/null +++ b/pkg-py/README.qmd @@ -0,0 +1,16 @@ +--- +title: "Brand YAML" +subtitle: "Unified Branding for Humans" + +format: gfm +--- + +```{python} +from brand_yaml import Brand + +brand = Brand( + meta = {"name": "Posit PBC", "link": "https://posit.co"} +) + +brand.meta +``` \ No newline at end of file diff --git a/pkg-py/pyproject.toml b/pkg-py/pyproject.toml new file mode 100644 index 00000000..117b3fdd --- /dev/null +++ b/pkg-py/pyproject.toml @@ -0,0 +1,25 @@ +[project] +name = "brand-yaml" +version = "0.1.0" +description = "Read brand yaml files, a unified way to store brand information." +readme = "README.md" +requires-python = ">=3.9" +dependencies = [ + "ruamel-yaml>=0.18.6", + "pydantic>=2.8.2", + "jsonschema>=4.23.0", +] + +[project.optional-dependencies] +quarto = [ + "pyyaml>=6.0.2", + "nbformat>=5.10.4", + "nbclient>=0.10.0", +] +dev = [ + "pytest>=8.3.2", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py new file mode 100644 index 00000000..e24ef041 --- /dev/null +++ b/pkg-py/src/brand_yaml/__init__.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from pathlib import Path + +from pydantic import BaseModel, ConfigDict +from ruamel.yaml import YAML + +from ._meta import BrandMeta + +yaml = YAML() + + +class Brand(BaseModel): + model_config = ConfigDict(extra="ignore", revalidate_instances="always") + + meta: BrandMeta = None + # logo: str | BrandLogo = None + # color: BrandColor = None + # typography: BrandTypography = None + # defaults: dict[str, Any] = None + + +def read_brand_yaml(path: str | Path) -> Brand: + path = Path(path) + + with open(path, "r") as f: + brand_data = yaml.load(f) + + return Brand.model_validate(brand_data) + + +__all__ = [ + "Brand", + "read_brand_yaml", +] diff --git a/pkg-py/src/brand_yaml/_meta.py b/pkg-py/src/brand_yaml/_meta.py new file mode 100644 index 00000000..33f39e41 --- /dev/null +++ b/pkg-py/src/brand_yaml/_meta.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, HttpUrl, Field + + +class BrandMeta(BaseModel): + """ + Brand metadata is stored in `meta`, providing place to describe the company + or project, the brand guidelines, additional links, and more. + """ + + model_config = ConfigDict(extra="allow", str_strip_whitespace=True) + + name: str | BrandMetaName = Field( + None, examples=["Very Big Corporation of America"] + ) + link: HttpUrl | BrandLink = Field( + None, + examples=[ + "https://very-big-corp.com", + '{"home": "https://very-big-corp.com"}', + ], + ) + + +class BrandMetaName(BaseModel): + model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) + + full: str = Field(None, examples=["Very Big Corporation of America"]) + short: str = Field(None, examples=["VBC"]) + + +class BrandLink(BaseModel): + model_config = ConfigDict(extra="allow", str_strip_whitespace=True) + + home: HttpUrl = Field( + None, + examples=["https://very-big-corp.com"], + ) + mastodon: HttpUrl = Field( + None, + examples=["https://mastodon.social/@VeryBigCorpOfficial"], + ) + github: HttpUrl = Field( + None, + examples=["https://github.com/Very-Big-Corp"], + ) + linkedin: HttpUrl = Field( + None, + examples=["https://linkedin.com/company/very-big-corp"], + ) + twitter: HttpUrl = Field( + None, + examples=["https://twitter.com/VeryBigCorp"], + ) + facebook: HttpUrl = Field( + None, + examples=["https://facebook.com/Very-Big-Corp"], + ) diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py new file mode 100644 index 00000000..7f5dee6b --- /dev/null +++ b/pkg-py/tests/test_meta.py @@ -0,0 +1,46 @@ +import pytest +from brand_yaml import read_brand_yaml +from brand_yaml._meta import BrandMeta +from pydantic import HttpUrl +from utils import path_examples + + +def test_brand_meta(): + meta = BrandMeta( + name={"full": "Very Big Corporation of America ", "short": " VBC "}, + link={"home": "https://very-big-corp.com"}, + ) + assert meta.name.full == "Very Big Corporation of America" + assert meta.name.short == "VBC" + assert meta.link.home == HttpUrl("https://very-big-corp.com/") + + +def test_brand_meta_bad_url(): + with pytest.raises(ValueError): + BrandMeta( + name={"full": "Very Big Corporation of America ", "short": " VBC "}, + link={"home": "not-a-url"}, + ) + + +def test_brand_meta_yaml_full(): + brand = read_brand_yaml(path_examples("brand-meta-full.yml")) + + assert brand.meta.name.full == "Very Big Corporation of America" + assert brand.meta.name.short == "VBC" + assert brand.meta.link.home == HttpUrl("https://very-big-corp.com") + assert brand.meta.link.mastodon == HttpUrl( + "https://mastodon.social/@VeryBigCorpOfficial" + ) + assert brand.meta.link.github == HttpUrl("https://github.com/Very-Big-Corp") + assert brand.meta.link.linkedin == HttpUrl( + "https://linkedin.com/company/very-big-corp" + ) + assert brand.meta.link.twitter == HttpUrl("https://twitter.com/VeryBigCorp") + assert brand.meta.link.facebook == HttpUrl("https://facebook.com/Very-Big-Corp") + +def test_brand_meta_yaml_small(): + brand = read_brand_yaml(path_examples("brand-meta-small.yml")) + + assert brand.meta.name == "Very Big Corp. of America" + assert brand.meta.link == HttpUrl("https://very-big-corp.com") diff --git a/pkg-py/tests/utils/__init__.py b/pkg-py/tests/utils/__init__.py new file mode 100644 index 00000000..ebc21261 --- /dev/null +++ b/pkg-py/tests/utils/__init__.py @@ -0,0 +1,5 @@ +from pathlib import Path + +def path_examples(*args) -> Path: + repo_root = Path(__file__).parent.parent.parent.parent + return repo_root / "examples" / Path(*args) diff --git a/pkg-py/uv.lock b/pkg-py/uv.lock new file mode 100644 index 00000000..312f1655 --- /dev/null +++ b/pkg-py/uv.lock @@ -0,0 +1,820 @@ +version = 1 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version < '3.10'", + "python_full_version < '3.11'", + "python_full_version < '3.12'", + "python_full_version < '3.13'", + "python_full_version < '3.10'", + "python_full_version == '3.10.*'", + "python_full_version == '3.11.*'", + "python_full_version == '3.12.*'", + "python_full_version < '3.13'", + "python_full_version >= '3.13' and python_full_version < '4.0'", + "python_full_version >= '4.0'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "attrs" +version = "24.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, +] + +[[package]] +name = "brand-yaml" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "ruamel-yaml" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest" }, +] +quarto = [ + { name = "nbclient" }, + { name = "nbformat" }, + { name = "pyyaml" }, +] + +[package.metadata] +requires-dist = [ + { name = "jsonschema", specifier = ">=4.23.0" }, + { name = "nbclient", marker = "extra == 'quarto'", specifier = ">=0.10.0" }, + { name = "nbformat", marker = "extra == 'quarto'", specifier = ">=5.10.4" }, + { name = "pydantic", specifier = ">=2.8.2" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.2" }, + { name = "pyyaml", marker = "extra == 'quarto'", specifier = ">=6.0.2" }, + { name = "ruamel-yaml", specifier = ">=0.18.6" }, +] + +[[package]] +name = "cffi" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/bf/82c351342972702867359cfeba5693927efe0a8dd568165490144f554b18/cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", size = 516073 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/2a/9071bf1e20bf9f695643b6c3e0f838f340b95ee29de0d1bb7968772409be/cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb", size = 181841 }, + { url = "https://files.pythonhosted.org/packages/4b/42/60116f10466d692b64aef32ac40fd79b11344ab6ef889ff8e3d047f2fcb2/cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a", size = 178242 }, + { url = "https://files.pythonhosted.org/packages/26/8e/a53f844454595c6e9215e56cda123db3427f8592f2c7b5ef1be782f620d6/cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42", size = 425676 }, + { url = "https://files.pythonhosted.org/packages/60/ac/6402563fb40b64c7ccbea87836d9c9498b374629af3449f3d8ff34df187d/cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d", size = 447842 }, + { url = "https://files.pythonhosted.org/packages/b2/e7/e2ffdb8de59f48f17b196813e9c717fbed2364e39b10bdb3836504e89486/cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2", size = 455224 }, + { url = "https://files.pythonhosted.org/packages/59/55/3e8968e92fe35c1c368959a070a1276c10cae29cdad0fd0daa36c69e237e/cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab", size = 436341 }, + { url = "https://files.pythonhosted.org/packages/7f/df/700aaf009dfbfa04acb1ed487586c03c788c6a312f0361ad5f298c5f5a7d/cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b", size = 445861 }, + { url = "https://files.pythonhosted.org/packages/5a/70/637f070aae533ea11ab77708a820f3935c0edb4fbcef9393b788e6f426a5/cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206", size = 460982 }, + { url = "https://files.pythonhosted.org/packages/f7/1a/7d4740fa1ccc4fcc888963fc3165d69ef1a2c8d42c8911c946703ff5d4a5/cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa", size = 438434 }, + { url = "https://files.pythonhosted.org/packages/d0/d9/c48cc38aaf6f53a8b5d2dbf6fe788410fcbab33b15a69c56c01d2b08f6a2/cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f", size = 461219 }, + { url = "https://files.pythonhosted.org/packages/26/ec/b6a7f660a7f27bd2bb53fe99a2ccafa279088395ec8639b25b8950985b2d/cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc", size = 171406 }, + { url = "https://files.pythonhosted.org/packages/08/42/8c00824787e6f5ec55194f5cd30c4ba4b9d9d5bb0d4d0007b1bb948d4ad4/cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2", size = 180809 }, + { url = "https://files.pythonhosted.org/packages/53/cc/9298fb6235522e00e47d78d6aa7f395332ef4e5f6fe124f9a03aa60600f7/cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", size = 181912 }, + { url = "https://files.pythonhosted.org/packages/e7/79/dc5334fbe60635d0846c56597a8d2af078a543ff22bc48d36551a0de62c2/cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", size = 178297 }, + { url = "https://files.pythonhosted.org/packages/39/d7/ef1b6b16b51ccbabaced90ff0d821c6c23567fc4b2e4a445aea25d3ceb92/cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", size = 444909 }, + { url = "https://files.pythonhosted.org/packages/29/b8/6e3c61885537d985c78ef7dd779b68109ba256263d74a2f615c40f44548d/cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", size = 468854 }, + { url = "https://files.pythonhosted.org/packages/0b/49/adad1228e19b931e523c2731e6984717d5f9e33a2f9971794ab42815b29b/cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", size = 476890 }, + { url = "https://files.pythonhosted.org/packages/76/54/c00f075c3e7fd14d9011713bcdb5b4f105ad044c5ad948db7b1a0a7e4e78/cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", size = 459374 }, + { url = "https://files.pythonhosted.org/packages/f3/b9/f163bb3fa4fbc636ee1f2a6a4598c096cdef279823ddfaa5734e556dd206/cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", size = 466891 }, + { url = "https://files.pythonhosted.org/packages/31/52/72bbc95f6d06ff2e88a6fa13786be4043e542cb24748e1351aba864cb0a7/cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91", size = 477658 }, + { url = "https://files.pythonhosted.org/packages/67/20/d694811457eeae0c7663fa1a7ca201ce495533b646c1180d4ac25684c69c/cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", size = 453890 }, + { url = "https://files.pythonhosted.org/packages/dc/79/40cbf5739eb4f694833db5a27ce7f63e30a9b25b4a836c4f25fb7272aacc/cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", size = 478254 }, + { url = "https://files.pythonhosted.org/packages/e9/eb/2c384c385cca5cae67ca10ac4ef685277680b8c552b99aedecf4ea23ff7e/cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", size = 171285 }, + { url = "https://files.pythonhosted.org/packages/ca/42/74cb1e0f1b79cb64672f3cb46245b506239c1297a20c0d9c3aeb3929cb0c/cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", size = 180842 }, + { url = "https://files.pythonhosted.org/packages/1a/1f/7862231350cc959a3138889d2c8d33da7042b22e923457dfd4cd487d772a/cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", size = 182826 }, + { url = "https://files.pythonhosted.org/packages/8b/8c/26119bf8b79e05a1c39812064e1ee7981e1f8a5372205ba5698ea4dd958d/cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", size = 178494 }, + { url = "https://files.pythonhosted.org/packages/61/94/4882c47d3ad396d91f0eda6ef16d45be3d752a332663b7361933039ed66a/cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", size = 454459 }, + { url = "https://files.pythonhosted.org/packages/0f/7c/a6beb119ad515058c5ee1829742d96b25b2b9204ff920746f6e13bf574eb/cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", size = 478502 }, + { url = "https://files.pythonhosted.org/packages/61/8a/2575cd01a90e1eca96a30aec4b1ac101a6fae06c49d490ac2704fa9bc8ba/cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", size = 485381 }, + { url = "https://files.pythonhosted.org/packages/cd/66/85899f5a9f152db49646e0c77427173e1b77a1046de0191ab3b0b9a5e6e3/cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", size = 470907 }, + { url = "https://files.pythonhosted.org/packages/00/13/150924609bf377140abe6e934ce0a57f3fc48f1fd956ec1f578ce97a4624/cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", size = 479074 }, + { url = "https://files.pythonhosted.org/packages/17/fd/7d73d7110155c036303b0a6462c56250e9bc2f4119d7591d27417329b4d1/cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", size = 484225 }, + { url = "https://files.pythonhosted.org/packages/fc/83/8353e5c9b01bb46332dac3dfb18e6c597a04ceb085c19c814c2f78a8c0d0/cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", size = 488388 }, + { url = "https://files.pythonhosted.org/packages/73/0c/f9d5ca9a095b1fc88ef77d1f8b85d11151c374144e4606da33874e17b65b/cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", size = 172096 }, + { url = "https://files.pythonhosted.org/packages/72/21/8c5d285fe20a6e31d29325f1287bb0e55f7d93630a5a44cafdafb5922495/cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", size = 181478 }, + { url = "https://files.pythonhosted.org/packages/17/8f/581f2f3c3464d5f7cf87c2f7a5ba9acc6976253e02d73804240964243ec2/cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", size = 182638 }, + { url = "https://files.pythonhosted.org/packages/8d/1c/c9afa66684b7039f48018eb11b229b659dfb32b7a16b88251bac106dd1ff/cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", size = 178453 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/1a134d479d3a5a1ff2fabbee551d1d3f1dd70f453e081b5f70d604aae4c0/cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", size = 454441 }, + { url = "https://files.pythonhosted.org/packages/b1/b4/e1569475d63aad8042b0935dbf62ae2a54d1e9142424e2b0e924d2d4a529/cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", size = 478543 }, + { url = "https://files.pythonhosted.org/packages/d2/40/a9ad03fbd64309dec5bb70bc803a9a6772602de0ee164d7b9a6ca5a89249/cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", size = 485463 }, + { url = "https://files.pythonhosted.org/packages/a6/1a/f10be60e006dd9242a24bcc2b1cd55c34c578380100f742d8c610f7a5d26/cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", size = 470854 }, + { url = "https://files.pythonhosted.org/packages/cc/b3/c035ed21aa3d39432bd749fe331ee90e4bc83ea2dbed1f71c4bc26c41084/cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", size = 479096 }, + { url = "https://files.pythonhosted.org/packages/00/cb/6f7edde01131de9382c89430b8e253b8c8754d66b63a62059663ceafeab2/cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", size = 484013 }, + { url = "https://files.pythonhosted.org/packages/b9/83/8e4e8c211ea940210d293e951bf06b1bfb90f2eeee590e9778e99b4a8676/cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", size = 488119 }, + { url = "https://files.pythonhosted.org/packages/5e/52/3f7cfbc4f444cb4f73ff17b28690d12436dde665f67d68f1e1687908ab6c/cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", size = 172122 }, + { url = "https://files.pythonhosted.org/packages/94/19/cf5baa07ee0f0e55eab7382459fbddaba0fdb0ba45973dd92556ae0d02db/cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", size = 181504 }, + { url = "https://files.pythonhosted.org/packages/96/22/7866bf5450d6a5b8cf4123abde25b2126fce03ac4efc1244a44367b01c65/cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2", size = 181868 }, + { url = "https://files.pythonhosted.org/packages/0c/03/934cd50132c1637a52ab41c093ff89b93086181f6cdc40d43185083818c1/cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759", size = 178261 }, + { url = "https://files.pythonhosted.org/packages/4a/1e/06c7bc7ed387e42f0ecdef2477a5b291455c2158bb7a565848ef96bba113/cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4", size = 424564 }, + { url = "https://files.pythonhosted.org/packages/b7/9b/43f26a558d192bb0691051153add44404af0adf6e3e35d5ce83340d41a92/cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82", size = 446854 }, + { url = "https://files.pythonhosted.org/packages/b5/5c/7777c4b0fc212caf180b20ec51da3d9fa00910d40f042004d33679f39ec7/cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf", size = 454217 }, + { url = "https://files.pythonhosted.org/packages/8f/90/a40b9821755bd3dfd2dd9a341b660cd57dfa2fc3bb9d8c4499477fa27ae3/cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058", size = 435285 }, + { url = "https://files.pythonhosted.org/packages/e1/d3/36e54b85f670400ff0440ab743fa0de66bdd89b8f54b7d2370708cdcb03f/cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932", size = 444868 }, + { url = "https://files.pythonhosted.org/packages/15/aa/62f87ceb24b03e42061050b1139864347fd73291d2b70b3daefd0c4fdaa8/cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693", size = 460136 }, + { url = "https://files.pythonhosted.org/packages/d4/b6/7abfb922035cc03d2a6c05b6e90f55d60bfea26ef97a2d10357b3f0bdbf3/cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3", size = 437565 }, + { url = "https://files.pythonhosted.org/packages/83/a8/306c52a4625eef30a6d7828c0c7ecaf9a11e1fc83efe506d6fcf980b68c7/cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4", size = 460284 }, + { url = "https://files.pythonhosted.org/packages/a8/05/4daca3a5d2af2af95828b35e65221d4f8afb6155c9d80a1ebda7a11348ab/cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb", size = 171382 }, + { url = "https://files.pythonhosted.org/packages/89/2d/ec3ae32daf8713681ded997aa2e6d68306c11a41627fb351201111ea0d24/cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29", size = 180820 }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/2495c4ac46b980e4ca1f6ad6db102322ef3ad2410b79fdde159a4b0f3b92/exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc", size = 28883 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, +] + +[[package]] +name = "fastjsonschema" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/3f/3ad5e7be13b4b8b55f4477141885ab2364f65d5f6ad5f7a9daffd634d066/fastjsonschema-2.20.0.tar.gz", hash = "sha256:3d48fc5300ee96f5d116f10fe6f28d938e6008f59a6a025c2649475b87f76a23", size = 373056 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/ca/086311cdfc017ec964b2436fe0c98c1f4efcb7e4c328956a22456e497655/fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a", size = 23543 }, +] + +[[package]] +name = "importlib-metadata" +version = "8.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/bd/fa8ce65b0a7d4b6d143ec23b0f5fd3f7ab80121078c465bc02baeaab22dc/importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5", size = 54320 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/14/362d31bf1076b21e1bcdcb0dc61944822ff263937b804a79231df2774d28/importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", size = 26269 }, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/b9/cc0cc592e7c195fb8a650c1d5990b10175cf13b4c97465c72ec841de9e4b/jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc", size = 13983 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/07/44bd408781594c4d0a027666ef27fab1e441b109dc3b76b4f836f8fd04fe/jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c", size = 18482 }, +] + +[[package]] +name = "jupyter-client" +version = "8.6.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jupyter-core" }, + { name = "python-dateutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/61/3cd51dea7878691919adc34ff6ad180f13bfe25fb8c7662a9ee6dc64e643/jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df", size = 341102 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/d3/c4bb02580bc0db807edb9a29b2d0c56031be1ef0d804336deb2699a470f6/jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f", size = 105901 }, +] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "platformdirs" }, + { name = "pywin32", marker = "platform_python_implementation != 'PyPy' and sys_platform == 'win32'" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/00/11/b56381fa6c3f4cc5d2cf54a7dbf98ad9aa0b339ef7a601d6053538b079a7/jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9", size = 87629 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, +] + +[[package]] +name = "nbclient" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "nbformat" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/d2/39bc36604f24bccd44d374ac34769bc58c53a1da5acd1e83f0165aa4940e/nbclient-0.10.0.tar.gz", hash = "sha256:4b3f1b7dba531e498449c4db4f53da339c91d449dc11e9af3a43b4eb5c5abb09", size = 62246 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/e8/00517a23d3eeaed0513e718fbc94aab26eaa1758f5690fc8578839791c79/nbclient-0.10.0-py3-none-any.whl", hash = "sha256:f13e3529332a1f1f81d82a53210322476a168bb7090a0289c795fe9cc11c9d3f", size = 25318 }, +] + +[[package]] +name = "nbformat" +version = "5.10.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastjsonschema" }, + { name = "jsonschema" }, + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/fd/91545e604bc3dad7dca9ed03284086039b294c6b3d75c0d2fa45f9e9caf3/nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a", size = 142749 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, +] + +[[package]] +name = "packaging" +version = "24.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.8.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/99/d0a5dca411e0a017762258013ba9905cd6e7baa9a3fd1fe8b6529472902e/pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", size = 739834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/fa/b7f815b8c9ad021c07f88875b601222ef5e70619391ade4a49234d12d278/pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8", size = 423875 }, +] + +[[package]] +name = "pydantic-core" +version = "2.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/e3/0d5ad91211dba310f7ded335f4dad871172b9cc9ce204f5a56d76ccd6247/pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4", size = 388371 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/9d/f30f080f745682e762512f3eef1f6e392c7d74a102e6e96de8a013a5db84/pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3", size = 1837257 }, + { url = "https://files.pythonhosted.org/packages/f2/89/77e7aebdd4a235497ac1e07f0a99e9f40e47f6e0f6783fe30500df08fc42/pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6", size = 1776715 }, + { url = "https://files.pythonhosted.org/packages/18/50/5a4e9120b395108c2a0441a425356c0d26a655d7c617288bec1c28b854ac/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a", size = 1789023 }, + { url = "https://files.pythonhosted.org/packages/c7/e5/f19e13ba86b968d024b56aa53f40b24828652ac026e5addd0ae49eeada02/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3", size = 1775598 }, + { url = "https://files.pythonhosted.org/packages/c9/c7/f3c29bed28bd022c783baba5bf9946c4f694cb837a687e62f453c81eb5c6/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1", size = 1977691 }, + { url = "https://files.pythonhosted.org/packages/41/3e/f62c2a05c554fff34570f6788617e9670c83ed7bc07d62a55cccd1bc0be6/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953", size = 2693214 }, + { url = "https://files.pythonhosted.org/packages/ae/49/8a6fe79d35e2f3bea566d8ea0e4e6f436d4f749d7838c8e8c4c5148ae706/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98", size = 2061047 }, + { url = "https://files.pythonhosted.org/packages/51/c6/585355c7c8561e11197dbf6333c57dd32f9f62165d48589b57ced2373d97/pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a", size = 1895106 }, + { url = "https://files.pythonhosted.org/packages/ce/23/829f6b87de0775919e82f8addef8b487ace1c77bb4cb754b217f7b1301b6/pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a", size = 1968506 }, + { url = "https://files.pythonhosted.org/packages/ca/2f/f8ca8f0c40b3ee0a4d8730a51851adb14c5eda986ec09f8d754b2fba784e/pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840", size = 2110217 }, + { url = "https://files.pythonhosted.org/packages/bb/a0/1876656c7b17eb69cc683452cce6bb890dd722222a71b3de57ddb512f561/pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250", size = 1709669 }, + { url = "https://files.pythonhosted.org/packages/be/4a/576524eefa9b301c088c4818dc50ff1c51a88fe29efd87ab75748ae15fd7/pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c", size = 1902386 }, + { url = "https://files.pythonhosted.org/packages/61/db/f6a724db226d990a329910727cfac43539ff6969edc217286dd05cda3ef6/pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312", size = 1834507 }, + { url = "https://files.pythonhosted.org/packages/9b/83/6f2bfe75209d557ae1c3550c1252684fc1827b8b12fbed84c3b4439e135d/pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88", size = 1773527 }, + { url = "https://files.pythonhosted.org/packages/93/ef/513ea76d7ca81f2354bb9c8d7839fc1157673e652613f7e1aff17d8ce05d/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc", size = 1787879 }, + { url = "https://files.pythonhosted.org/packages/31/0a/ac294caecf235f0cc651de6232f1642bb793af448d1cfc541b0dc1fd72b8/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43", size = 1774694 }, + { url = "https://files.pythonhosted.org/packages/46/a4/08f12b5512f095963550a7cb49ae010e3f8f3f22b45e508c2cb4d7744fce/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6", size = 1976369 }, + { url = "https://files.pythonhosted.org/packages/15/59/b2495be4410462aedb399071c71884042a2c6443319cbf62d00b4a7ed7a5/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121", size = 2691250 }, + { url = "https://files.pythonhosted.org/packages/3c/ae/fc99ce1ba791c9e9d1dee04ce80eef1dae5b25b27e3fc8e19f4e3f1348bf/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1", size = 2061462 }, + { url = "https://files.pythonhosted.org/packages/44/bb/eb07cbe47cfd638603ce3cb8c220f1a054b821e666509e535f27ba07ca5f/pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b", size = 1893923 }, + { url = "https://files.pythonhosted.org/packages/ce/ef/5a52400553b8faa0e7f11fd7a2ba11e8d2feb50b540f9e7973c49b97eac0/pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27", size = 1966779 }, + { url = "https://files.pythonhosted.org/packages/4c/5b/fb37fe341344d9651f5c5f579639cd97d50a457dc53901aa8f7e9f28beb9/pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b", size = 2109044 }, + { url = "https://files.pythonhosted.org/packages/70/1a/6f7278802dbc66716661618807ab0dfa4fc32b09d1235923bbbe8b3a5757/pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a", size = 1708265 }, + { url = "https://files.pythonhosted.org/packages/35/7f/58758c42c61b0bdd585158586fecea295523d49933cb33664ea888162daf/pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2", size = 1901750 }, + { url = "https://files.pythonhosted.org/packages/6f/47/ef0d60ae23c41aced42921728650460dc831a0adf604bfa66b76028cb4d0/pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231", size = 1839225 }, + { url = "https://files.pythonhosted.org/packages/6a/23/430f2878c9cd977a61bb39f71751d9310ec55cee36b3d5bf1752c6341fd0/pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9", size = 1768604 }, + { url = "https://files.pythonhosted.org/packages/9e/2b/ec4e7225dee79e0dc80ccc3c35ab33cc2c4bbb8a1a7ecf060e5e453651ec/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f", size = 1789767 }, + { url = "https://files.pythonhosted.org/packages/64/b0/38b24a1fa6d2f96af3148362e10737ec073768cd44d3ec21dca3be40a519/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52", size = 1772061 }, + { url = "https://files.pythonhosted.org/packages/5e/da/bb73274c42cb60decfa61e9eb0c9029da78b3b9af0a9de0309dbc8ff87b6/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237", size = 1974573 }, + { url = "https://files.pythonhosted.org/packages/c8/65/41693110fb3552556180460daffdb8bbeefb87fc026fd9aa4b849374015c/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe", size = 2625596 }, + { url = "https://files.pythonhosted.org/packages/09/b3/a5a54b47cccd1ab661ed5775235c5e06924753c2d4817737c5667bfa19a8/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e", size = 2099064 }, + { url = "https://files.pythonhosted.org/packages/52/fa/443a7a6ea54beaba45ff3a59f3d3e6e3004b7460bcfb0be77bcf98719d3b/pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24", size = 1900345 }, + { url = "https://files.pythonhosted.org/packages/8e/e6/9aca9ffae60f9cdf0183069de3e271889b628d0fb175913fcb3db5618fb1/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1", size = 1968252 }, + { url = "https://files.pythonhosted.org/packages/46/5e/6c716810ea20a6419188992973a73c2fb4eb99cd382368d0637ddb6d3c99/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", size = 2119191 }, + { url = "https://files.pythonhosted.org/packages/06/fc/6123b00a9240fbb9ae0babad7a005d51103d9a5d39c957a986f5cdd0c271/pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", size = 1717788 }, + { url = "https://files.pythonhosted.org/packages/d5/36/e61ad5a46607a469e2786f398cd671ebafcd9fb17f09a2359985c7228df5/pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", size = 1898188 }, + { url = "https://files.pythonhosted.org/packages/49/75/40b0e98b658fdba02a693b3bacb4c875a28bba87796c7b13975976597d8c/pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686", size = 1838688 }, + { url = "https://files.pythonhosted.org/packages/75/02/d8ba2d4a266591a6a623c68b331b96523d4b62ab82a951794e3ed8907390/pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a", size = 1768409 }, + { url = "https://files.pythonhosted.org/packages/91/ae/25ecd9bc4ce4993e99a1a3c9ab111c082630c914260e129572fafed4ecc2/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b", size = 1789317 }, + { url = "https://files.pythonhosted.org/packages/7a/80/72057580681cdbe55699c367963d9c661b569a1d39338b4f6239faf36cdc/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19", size = 1771949 }, + { url = "https://files.pythonhosted.org/packages/a2/be/d9bbabc55b05019013180f141fcaf3b14dbe15ca7da550e95b60c321009a/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac", size = 1974392 }, + { url = "https://files.pythonhosted.org/packages/79/2d/7bcd938c6afb0f40293283f5f09988b61fb0a4f1d180abe7c23a2f665f8e/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703", size = 2625565 }, + { url = "https://files.pythonhosted.org/packages/ac/88/ca758e979457096008a4b16a064509028e3e092a1e85a5ed6c18ced8da88/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c", size = 2098784 }, + { url = "https://files.pythonhosted.org/packages/eb/de/2fad6d63c3c42e472e985acb12ec45b7f56e42e6f4cd6dfbc5e87ee8678c/pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83", size = 1900198 }, + { url = "https://files.pythonhosted.org/packages/fe/50/077c7f35b6488dc369a6d22993af3a37901e198630f38ac43391ca730f5b/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203", size = 1968005 }, + { url = "https://files.pythonhosted.org/packages/5d/1f/f378631574ead46d636b9a04a80ff878b9365d4b361b1905ef1667d4182a/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0", size = 2118920 }, + { url = "https://files.pythonhosted.org/packages/7a/ea/e4943f17df7a3031d709481fe4363d4624ae875a6409aec34c28c9e6cf59/pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e", size = 1717397 }, + { url = "https://files.pythonhosted.org/packages/13/63/b95781763e8d84207025071c0cec16d921c0163c7a9033ae4b9a0e020dc7/pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20", size = 1898013 }, + { url = "https://files.pythonhosted.org/packages/17/c3/803028de61ce9a1fe1643f77ff845807c76298bf1995fa216c4ae853c6b9/pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c", size = 1838087 }, + { url = "https://files.pythonhosted.org/packages/77/f7/25f1fba7ea1ae052e20b234e4c66d54b129e5b3f4d1e6c0da6534dbf57c3/pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6", size = 1722218 }, + { url = "https://files.pythonhosted.org/packages/57/53/fe2e1ae3795b7a69f81913584174f8ed36446b56df734565260830a3632b/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2", size = 1788970 }, + { url = "https://files.pythonhosted.org/packages/13/80/d9c698486f8fb64b0945e0844c95eef3bcff920941eda30d556deadadbdf/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a", size = 1775836 }, + { url = "https://files.pythonhosted.org/packages/0f/0c/ab6df185529c0ce1a6d916f9d159de389cc7de44eaa9362efc76495fb821/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611", size = 1978099 }, + { url = "https://files.pythonhosted.org/packages/0e/9f/3094afeb286c60ec08088d938b661a561f3d23cd2e88a90a92ab0ecfce4f/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b", size = 2693403 }, + { url = "https://files.pythonhosted.org/packages/9b/f1/a006955715be98093d092aa025f604c7c00721e83fe04bf467c49f31a685/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006", size = 2061754 }, + { url = "https://files.pythonhosted.org/packages/32/f6/cd2e7bd0a52e2a72841f60c32e62b269995c34bdb13e4d1e799be834338a/pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1", size = 1895490 }, + { url = "https://files.pythonhosted.org/packages/ac/22/34ce27579901fcca525f8adce7747760407cf284c4f0fec6d4542265b451/pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09", size = 1968678 }, + { url = "https://files.pythonhosted.org/packages/2f/3a/80df9b0b5ea5e5b8939285c600dc9ce4a185317f5fb065a37e77a20cbdb3/pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab", size = 2110873 }, + { url = "https://files.pythonhosted.org/packages/ec/26/998c9b8dadcdeafbc833964ef5975cd0c7516b0157575b26300d078ae239/pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2", size = 1709309 }, + { url = "https://files.pythonhosted.org/packages/ed/36/67aeb15996618882c5cfe85dbeffefe09e2806cd86bdd37bca40753e82a1/pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669", size = 1905736 }, + { url = "https://files.pythonhosted.org/packages/73/73/0c7265903f66cce39ed7ca939684fba344210cefc91ccc999cfd5b113fd3/pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906", size = 1828190 }, + { url = "https://files.pythonhosted.org/packages/27/55/60b8b0e58b49ee3ed36a18562dd7c6bc06a551c390e387af5872a238f2ec/pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94", size = 1715252 }, + { url = "https://files.pythonhosted.org/packages/28/3d/d66314bad6bb777a36559195a007b31e916bd9e2c198f7bb8f4ccdceb4fa/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f", size = 1782641 }, + { url = "https://files.pythonhosted.org/packages/9e/f5/f178f4354d0d6c1431a8f9ede71f3c4269ac4dc55d314fdb7555814276dc/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482", size = 1928788 }, + { url = "https://files.pythonhosted.org/packages/9c/51/1f5e27bb194df79e30b593b608c66e881ed481241e2b9ed5bdf86d165480/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6", size = 1886116 }, + { url = "https://files.pythonhosted.org/packages/ac/76/450d9258c58dc7c70b9e3aadf6bebe23ddd99e459c365e2adbde80e238da/pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc", size = 1960125 }, + { url = "https://files.pythonhosted.org/packages/dd/9e/0309a7a4bea51771729515e413b3987be0789837de99087f7415e0db1f9b/pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99", size = 2100407 }, + { url = "https://files.pythonhosted.org/packages/af/93/06d44e08277b3b818b75bd5f25e879d7693e4b7dd3505fde89916fcc9ca2/pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6", size = 1914966 }, + { url = "https://files.pythonhosted.org/packages/ff/d0/639b12bc7c81ebcbbd5f946327e8970089b23fa5b11d7abb56495cbdc0de/pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331", size = 1829108 }, + { url = "https://files.pythonhosted.org/packages/f1/80/3b9d7fb8b4f8d36e24373334740c0b88d9ded08342543a72e9247b4fa410/pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad", size = 1716448 }, + { url = "https://files.pythonhosted.org/packages/2f/c6/f80ea0fac8c241c066245fe918cdc9d105985a1a8726aced9478548c9e37/pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1", size = 1783620 }, + { url = "https://files.pythonhosted.org/packages/d5/3e/9af260156f79347ed3e64149836d69bfe1e0c5efadec6116a879fc31c9ec/pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86", size = 1929929 }, + { url = "https://files.pythonhosted.org/packages/d1/fe/8c3e928e10a97eb8e85b18a53ed3288d039cf0fd7b0fe8d3258f14e8500a/pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e", size = 1886708 }, + { url = "https://files.pythonhosted.org/packages/31/26/b670bd58f1de902c099ff623fe62b9820448a20d70437e7698a57b922d3a/pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0", size = 1960709 }, + { url = "https://files.pythonhosted.org/packages/de/ee/322cad098a0cffc81e985ac2a298d3f29a1da25efe7dc1fb5cd2615c5b04/pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a", size = 2101218 }, + { url = "https://files.pythonhosted.org/packages/07/8b/30233f741e16b35499fa2fad2f4a69eb127eec6c850a1b14af26e7b08b73/pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7", size = 1915399 }, +] + +[[package]] +name = "pytest" +version = "8.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/8c/9862305bdcd6020bc7b45b1b5e7397a6caf1a33d3025b9a003b39075ffb2/pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce", size = 1439314 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/f9/cf155cf32ca7d6fa3601bc4c5dd19086af4b320b706919d48a4c79081cf9/pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", size = 341802 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "pywin32" +version = "306" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/dc/28c668097edfaf4eac4617ef7adf081b9cf50d254672fcf399a70f5efc41/pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d", size = 8506422 }, + { url = "https://files.pythonhosted.org/packages/d3/d6/891894edec688e72c2e308b3243fad98b4066e1839fd2fe78f04129a9d31/pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8", size = 9226392 }, + { url = "https://files.pythonhosted.org/packages/8b/1e/fc18ad83ca553e01b97aa8393ff10e33c1fb57801db05488b83282ee9913/pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407", size = 8507689 }, + { url = "https://files.pythonhosted.org/packages/7e/9e/ad6b1ae2a5ad1066dc509350e0fbf74d8d50251a51e420a2a8feaa0cecbd/pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e", size = 9227547 }, + { url = "https://files.pythonhosted.org/packages/91/20/f744bff1da8f43388498503634378dbbefbe493e65675f2cc52f7185c2c2/pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a", size = 10388324 }, + { url = "https://files.pythonhosted.org/packages/14/91/17e016d5923e178346aabda3dfec6629d1a26efe587d19667542105cf0a6/pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b", size = 8507705 }, + { url = "https://files.pythonhosted.org/packages/83/1c/25b79fc3ec99b19b0a0730cc47356f7e2959863bf9f3cd314332bddb4f68/pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e", size = 9227429 }, + { url = "https://files.pythonhosted.org/packages/1c/43/e3444dc9a12f8365d9603c2145d16bf0a2f8180f343cf87be47f5579e547/pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040", size = 10388145 }, + { url = "https://files.pythonhosted.org/packages/7e/7f/419c4fcadcaa374a0ae41cbdf6c3a81452892dd6c523aea629d17e49146e/pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802", size = 8573451 }, + { url = "https://files.pythonhosted.org/packages/1c/f7/24d8ed4fd9c43b90354df7764f81f0dd5e623f9a50f1538f90fe085d6dff/pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4", size = 9312883 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 }, + { url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 }, + { url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 }, + { url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 }, + { url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 }, + { url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 }, + { url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 }, + { url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 }, + { url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 }, + { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 }, + { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 }, + { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 }, + { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 }, + { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 }, + { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 }, + { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 }, + { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 }, + { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 }, + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, + { url = "https://files.pythonhosted.org/packages/65/d8/b7a1db13636d7fb7d4ff431593c510c8b8fca920ade06ca8ef20015493c5/PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", size = 184777 }, + { url = "https://files.pythonhosted.org/packages/0a/02/6ec546cd45143fdf9840b2c6be8d875116a64076218b61d68e12548e5839/PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", size = 172318 }, + { url = "https://files.pythonhosted.org/packages/0e/9a/8cc68be846c972bda34f6c2a93abb644fb2476f4dcc924d52175786932c9/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", size = 720891 }, + { url = "https://files.pythonhosted.org/packages/e9/6c/6e1b7f40181bc4805e2e07f4abc10a88ce4648e7e95ff1abe4ae4014a9b2/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", size = 722614 }, + { url = "https://files.pythonhosted.org/packages/3d/32/e7bd8535d22ea2874cef6a81021ba019474ace0d13a4819c2a4bce79bd6a/PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", size = 737360 }, + { url = "https://files.pythonhosted.org/packages/d7/12/7322c1e30b9be969670b672573d45479edef72c9a0deac3bb2868f5d7469/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", size = 699006 }, + { url = "https://files.pythonhosted.org/packages/82/72/04fcad41ca56491995076630c3ec1e834be241664c0c09a64c9a2589b507/PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", size = 723577 }, + { url = "https://files.pythonhosted.org/packages/ed/5e/46168b1f2757f1fcd442bc3029cd8767d88a98c9c05770d8b420948743bb/PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", size = 144593 }, + { url = "https://files.pythonhosted.org/packages/19/87/5124b1c1f2412bb95c59ec481eaf936cd32f0fe2a7b16b97b81c4c017a6a/PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", size = 162312 }, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "implementation_name == 'pypy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/05/bed626b9f7bb2322cdbbf7b4bd8f54b1b617b0d2ab2d3547d6e39428a48e/pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f", size = 271975 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/a8/9837c39aba390eb7d01924ace49d761c8dbe7bc2d6082346d00c8332e431/pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629", size = 1340058 }, + { url = "https://files.pythonhosted.org/packages/a2/1f/a006f2e8e4f7d41d464272012695da17fb95f33b54342612a6890da96ff6/pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b", size = 1008818 }, + { url = "https://files.pythonhosted.org/packages/b6/09/b51b6683fde5ca04593a57bbe81788b6b43114d8f8ee4e80afc991e14760/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764", size = 673199 }, + { url = "https://files.pythonhosted.org/packages/c9/78/486f3e2e824f3a645238332bf5a4c4b4477c3063033a27c1e4052358dee2/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c", size = 911762 }, + { url = "https://files.pythonhosted.org/packages/5e/3b/2eb1667c9b866f53e76ee8b0c301b0469745a23bd5a87b7ee3d5dd9eb6e5/pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a", size = 868773 }, + { url = "https://files.pythonhosted.org/packages/16/29/ca99b4598a9dc7e468b5417eda91f372b595be1e3eec9b7cbe8e5d3584e8/pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88", size = 868834 }, + { url = "https://files.pythonhosted.org/packages/ad/e5/9efaeb1d2f4f8c50da04144f639b042bc52869d3a206d6bf672ab3522163/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f", size = 1202861 }, + { url = "https://files.pythonhosted.org/packages/c3/62/c721b5608a8ac0a69bb83cbb7d07a56f3ff00b3991a138e44198a16f94c7/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282", size = 1515304 }, + { url = "https://files.pythonhosted.org/packages/87/84/e8bd321aa99b72f48d4606fc5a0a920154125bd0a4608c67eab742dab087/pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea", size = 1414712 }, + { url = "https://files.pythonhosted.org/packages/cd/cd/420e3fd1ac6977b008b72e7ad2dae6350cc84d4c5027fc390b024e61738f/pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2", size = 578113 }, + { url = "https://files.pythonhosted.org/packages/5c/57/73930d56ed45ae0cb4946f383f985c855c9b3d4063f26416998f07523c0e/pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971", size = 641631 }, + { url = "https://files.pythonhosted.org/packages/61/d2/ae6ac5c397f1ccad59031c64beaafce7a0d6182e0452cc48f1c9c87d2dd0/pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa", size = 543528 }, + { url = "https://files.pythonhosted.org/packages/12/20/de7442172f77f7c96299a0ac70e7d4fb78cd51eca67aa2cf552b66c14196/pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218", size = 1340639 }, + { url = "https://files.pythonhosted.org/packages/98/4d/5000468bd64c7910190ed0a6c76a1ca59a68189ec1f007c451dc181a22f4/pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4", size = 1008710 }, + { url = "https://files.pythonhosted.org/packages/e1/bf/c67fd638c2f9fbbab8090a3ee779370b97c82b84cc12d0c498b285d7b2c0/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef", size = 673129 }, + { url = "https://files.pythonhosted.org/packages/86/94/99085a3f492aa538161cbf27246e8886ff850e113e0c294a5b8245f13b52/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317", size = 910107 }, + { url = "https://files.pythonhosted.org/packages/31/1d/346809e8a9b999646d03f21096428453465b1bca5cd5c64ecd048d9ecb01/pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf", size = 867960 }, + { url = "https://files.pythonhosted.org/packages/ab/68/6fb6ae5551846ad5beca295b7bca32bf0a7ce19f135cb30e55fa2314e6b6/pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e", size = 869204 }, + { url = "https://files.pythonhosted.org/packages/0f/f9/18417771dee223ccf0f48e29adf8b4e25ba6d0e8285e33bcbce078070bc3/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37", size = 1203351 }, + { url = "https://files.pythonhosted.org/packages/e0/46/f13e67fe0d4f8a2315782cbad50493de6203ea0d744610faf4d5f5b16e90/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3", size = 1514204 }, + { url = "https://files.pythonhosted.org/packages/50/11/ddcf7343b7b7a226e0fc7b68cbf5a5bb56291fac07f5c3023bb4c319ebb4/pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6", size = 1414339 }, + { url = "https://files.pythonhosted.org/packages/01/14/1c18d7d5b7be2708f513f37c61bfadfa62161c10624f8733f1c8451b3509/pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4", size = 576928 }, + { url = "https://files.pythonhosted.org/packages/3b/1b/0a540edd75a41df14ec416a9a500b9fec66e554aac920d4c58fbd5756776/pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5", size = 642317 }, + { url = "https://files.pythonhosted.org/packages/98/77/1cbfec0358078a4c5add529d8a70892db1be900980cdb5dd0898b3d6ab9d/pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003", size = 543834 }, + { url = "https://files.pythonhosted.org/packages/28/2f/78a766c8913ad62b28581777ac4ede50c6d9f249d39c2963e279524a1bbe/pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9", size = 1343105 }, + { url = "https://files.pythonhosted.org/packages/b7/9c/4b1e2d3d4065be715e007fe063ec7885978fad285f87eae1436e6c3201f4/pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52", size = 1008365 }, + { url = "https://files.pythonhosted.org/packages/4f/ef/5a23ec689ff36d7625b38d121ef15abfc3631a9aecb417baf7a4245e4124/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08", size = 665923 }, + { url = "https://files.pythonhosted.org/packages/ae/61/d436461a47437d63c6302c90724cf0981883ec57ceb6073873f32172d676/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5", size = 903400 }, + { url = "https://files.pythonhosted.org/packages/47/42/fc6d35ecefe1739a819afaf6f8e686f7f02a4dd241c78972d316f403474c/pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae", size = 860034 }, + { url = "https://files.pythonhosted.org/packages/07/3b/44ea6266a6761e9eefaa37d98fabefa112328808ac41aa87b4bbb668af30/pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711", size = 860579 }, + { url = "https://files.pythonhosted.org/packages/38/6f/4df2014ab553a6052b0e551b37da55166991510f9e1002c89cab7ce3b3f2/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6", size = 1196246 }, + { url = "https://files.pythonhosted.org/packages/38/9d/ee240fc0c9fe9817f0c9127a43238a3e28048795483c403cc10720ddef22/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3", size = 1507441 }, + { url = "https://files.pythonhosted.org/packages/85/4f/01711edaa58d535eac4a26c294c617c9a01f09857c0ce191fd574d06f359/pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b", size = 1406498 }, + { url = "https://files.pythonhosted.org/packages/07/18/907134c85c7152f679ed744e73e645b365f3ad571f38bdb62e36f347699a/pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7", size = 575533 }, + { url = "https://files.pythonhosted.org/packages/ce/2c/a6f4a20202a4d3c582ad93f95ee78d79bbdc26803495aec2912b17dbbb6c/pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a", size = 637768 }, + { url = "https://files.pythonhosted.org/packages/5f/0e/eb16ff731632d30554bf5af4dbba3ffcd04518219d82028aea4ae1b02ca5/pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b", size = 540675 }, + { url = "https://files.pythonhosted.org/packages/04/a7/0f7e2f6c126fe6e62dbae0bc93b1bd3f1099cf7fea47a5468defebe3f39d/pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726", size = 1006564 }, + { url = "https://files.pythonhosted.org/packages/31/b6/a187165c852c5d49f826a690857684333a6a4a065af0a6015572d2284f6a/pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3", size = 1340447 }, + { url = "https://files.pythonhosted.org/packages/68/ba/f4280c58ff71f321602a6e24fd19879b7e79793fb8ab14027027c0fb58ef/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50", size = 665485 }, + { url = "https://files.pythonhosted.org/packages/77/b5/c987a5c53c7d8704216f29fc3d810b32f156bcea488a940e330e1bcbb88d/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb", size = 903484 }, + { url = "https://files.pythonhosted.org/packages/29/c9/07da157d2db18c72a7eccef8e684cefc155b712a88e3d479d930aa9eceba/pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187", size = 859981 }, + { url = "https://files.pythonhosted.org/packages/43/09/e12501bd0b8394b7d02c41efd35c537a1988da67fc9c745cae9c6c776d31/pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b", size = 860334 }, + { url = "https://files.pythonhosted.org/packages/eb/ff/f5ec1d455f8f7385cc0a8b2acd8c807d7fade875c14c44b85c1bddabae21/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18", size = 1196179 }, + { url = "https://files.pythonhosted.org/packages/ec/8a/bb2ac43295b1950fe436a81fc5b298be0b96ac76fb029b514d3ed58f7b27/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115", size = 1507668 }, + { url = "https://files.pythonhosted.org/packages/a9/49/dbc284ebcfd2dca23f6349227ff1616a7ee2c4a35fe0a5d6c3deff2b4fed/pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e", size = 1406539 }, + { url = "https://files.pythonhosted.org/packages/00/68/093cdce3fe31e30a341d8e52a1ad86392e13c57970d722c1f62a1d1a54b6/pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5", size = 575567 }, + { url = "https://files.pythonhosted.org/packages/92/ae/6cc4657148143412b5819b05e362ae7dd09fb9fe76e2a539dcff3d0386bc/pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad", size = 637551 }, + { url = "https://files.pythonhosted.org/packages/6c/67/fbff102e201688f97c8092e4c3445d1c1068c2f27bbd45a578df97ed5f94/pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797", size = 540378 }, + { url = "https://files.pythonhosted.org/packages/3f/fe/2d998380b6e0122c6c4bdf9b6caf490831e5f5e2d08a203b5adff060c226/pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a", size = 1007378 }, + { url = "https://files.pythonhosted.org/packages/4a/f4/30d6e7157f12b3a0390bde94d6a8567cdb88846ed068a6e17238a4ccf600/pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc", size = 1329532 }, + { url = "https://files.pythonhosted.org/packages/82/86/3fe917870e15ee1c3ad48229a2a64458e36036e64b4afa9659045d82bfa8/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5", size = 653242 }, + { url = "https://files.pythonhosted.org/packages/50/2d/242e7e6ef6c8c19e6cb52d095834508cd581ffb925699fd3c640cdc758f1/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672", size = 888404 }, + { url = "https://files.pythonhosted.org/packages/ac/11/7270566e1f31e4ea73c81ec821a4b1688fd551009a3d2bab11ec66cb1e8f/pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797", size = 845858 }, + { url = "https://files.pythonhosted.org/packages/91/d5/72b38fbc69867795c8711bdd735312f9fef1e3d9204e2f63ab57085434b9/pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386", size = 847375 }, + { url = "https://files.pythonhosted.org/packages/dd/9a/10ed3c7f72b4c24e719c59359fbadd1a27556a28b36cdf1cd9e4fb7845d5/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306", size = 1183489 }, + { url = "https://files.pythonhosted.org/packages/72/2d/8660892543fabf1fe41861efa222455811adac9f3c0818d6c3170a1153e3/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6", size = 1492932 }, + { url = "https://files.pythonhosted.org/packages/7b/d6/32fd69744afb53995619bc5effa2a405ae0d343cd3e747d0fbc43fe894ee/pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0", size = 1392485 }, + { url = "https://files.pythonhosted.org/packages/ac/9e/ad5fbbe1bcc7a9d1e8c5f4f7de48f2c1dc481e151ef80cc1ce9a7fe67b55/pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2", size = 1341256 }, + { url = "https://files.pythonhosted.org/packages/4c/d9/d7a8022108c214803a82b0b69d4885cee00933d21928f1f09dca371cf4bf/pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c", size = 1009385 }, + { url = "https://files.pythonhosted.org/packages/ed/69/0529b59ac667ea8bfe8796ac71796b688fbb42ff78e06525dabfed3bc7ae/pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98", size = 908009 }, + { url = "https://files.pythonhosted.org/packages/6e/bd/3ff3e1172f12f55769793a3a334e956ec2886805ebfb2f64756b6b5c6a1a/pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9", size = 862078 }, + { url = "https://files.pythonhosted.org/packages/c3/ec/ab13585c3a1f48e2874253844c47b194d56eb25c94718691349c646f336f/pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db", size = 673756 }, + { url = "https://files.pythonhosted.org/packages/1e/be/febcd4b04dd50ee6d514dfbc33a3d5d9cb38ec9516e02bbfc929baa0f141/pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073", size = 1203684 }, + { url = "https://files.pythonhosted.org/packages/16/28/304150e71afd2df3b82f52f66c0d8ab9ac6fe1f1ffdf92bad4c8cc91d557/pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc", size = 1515864 }, + { url = "https://files.pythonhosted.org/packages/18/89/8d48d8cd505c12a1f5edee597cc32ffcedc65fd8d2603aebaaedc38a7041/pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940", size = 1415383 }, + { url = "https://files.pythonhosted.org/packages/d4/7e/43a60c3b179f7da0cbc2b649bd2702fd6a39bff5f72aa38d6e1aeb00256d/pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44", size = 578540 }, + { url = "https://files.pythonhosted.org/packages/3a/55/8841dcd28f783ad06674c8fe8d7d72794b548d0bff8829aaafeb72e8b44d/pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec", size = 642147 }, + { url = "https://files.pythonhosted.org/packages/b4/78/b3c31ccfcfcdd6ea50b6abc8f46a2a7aadb9c3d40531d1b908d834aaa12e/pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb", size = 543903 }, + { url = "https://files.pythonhosted.org/packages/53/fb/36b2b2548286e9444e52fcd198760af99fd89102b5be50f0660fcfe902df/pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072", size = 906955 }, + { url = "https://files.pythonhosted.org/packages/77/8f/6ce54f8979a01656e894946db6299e2273fcee21c8e5fa57c6295ef11f57/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1", size = 565701 }, + { url = "https://files.pythonhosted.org/packages/ee/1c/bf8cd66730a866b16db8483286078892b7f6536f8c389fb46e4beba0a970/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d", size = 794312 }, + { url = "https://files.pythonhosted.org/packages/71/43/91fa4ff25bbfdc914ab6bafa0f03241d69370ef31a761d16bb859f346582/pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca", size = 752775 }, + { url = "https://files.pythonhosted.org/packages/ec/d2/3b2ab40f455a256cb6672186bea95cd97b459ce4594050132d71e76f0d6f/pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c", size = 550762 }, + { url = "https://files.pythonhosted.org/packages/6c/78/3096d72581365dfb0081ac9512a3b53672fa69854aa174d78636510c4db8/pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3", size = 906945 }, + { url = "https://files.pythonhosted.org/packages/da/f2/8054574d77c269c31d055d4daf3d8407adf61ea384a50c8d14b158551d09/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a", size = 565698 }, + { url = "https://files.pythonhosted.org/packages/77/21/c3ad93236d1d60eea10b67528f55e7db115a9d32e2bf163fcf601f85e9cc/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6", size = 794307 }, + { url = "https://files.pythonhosted.org/packages/6a/49/e95b491724500fcb760178ce8db39b923429e328e57bcf9162e32c2c187c/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a", size = 752769 }, + { url = "https://files.pythonhosted.org/packages/9b/a9/50c9c06762b30792f71aaad8d1886748d39c4bffedc1171fbc6ad2b92d67/pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4", size = 751338 }, + { url = "https://files.pythonhosted.org/packages/ca/63/27e6142b4f67a442ee480986ca5b88edb01462dd2319843057683a5148bd/pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f", size = 550757 }, +] + +[[package]] +name = "referencing" +version = "0.35.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/73ca1f8e72fff6fa52119dbd185f73a907b1989428917b24cff660129b6d/referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c", size = 62991 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/59/2056f61236782a2c86b33906c025d4f4a0b17be0161b63b70fd9e8775d36/referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de", size = 26684 }, +] + +[[package]] +name = "rpds-py" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/55/64/b693f262791b818880d17268f3f8181ef799b0d187f6f731b1772e05a29a/rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121", size = 25814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/2d/a7e60483b72b91909e18f29a5c5ae847bac4e2ae95b77bb77e1f41819a58/rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2", size = 318432 }, + { url = "https://files.pythonhosted.org/packages/b5/b4/f15b0c55a6d880ce74170e7e28c3ed6c5acdbbd118df50b91d1dabf86008/rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f", size = 311333 }, + { url = "https://files.pythonhosted.org/packages/36/10/3f4e490fe6eb069c07c22357d0b4804cd94cb9f8d01345ef9b1d93482b9d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150", size = 366697 }, + { url = "https://files.pythonhosted.org/packages/f5/c8/cd6ab31b4424c7fab3b17e153b6ea7d1bb0d7cabea5c1ef683cc8adb8bc2/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e", size = 368386 }, + { url = "https://files.pythonhosted.org/packages/60/5e/642a44fda6dda90b5237af7a0ef1d088159c30a504852b94b0396eb62125/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2", size = 395374 }, + { url = "https://files.pythonhosted.org/packages/7c/b5/ff18c093c9e72630f6d6242e5ccb0728ef8265ba0a154b5972f89d23790a/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3", size = 433189 }, + { url = "https://files.pythonhosted.org/packages/4a/6d/1166a157b227f2333f8e8ae320b6b7ea2a6a38fbe7a3563ad76dffc8608d/rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf", size = 354849 }, + { url = "https://files.pythonhosted.org/packages/70/a4/70ea49863ea09ae4c2971f2eef58e80b757e3c0f2f618c5815bb751f7847/rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140", size = 373233 }, + { url = "https://files.pythonhosted.org/packages/3b/d3/822a28152a1e7e2ba0dc5d06cf8736f4cd64b191bb6ec47fb51d1c3c5ccf/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f", size = 541852 }, + { url = "https://files.pythonhosted.org/packages/c6/a5/6ef91e4425dc8b3445ff77d292fc4c5e37046462434a0423c4e0a596a8bd/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce", size = 547630 }, + { url = "https://files.pythonhosted.org/packages/72/f8/d5625ee05c4e5c478954a16d9359069c66fe8ac8cd5ddf28f80d3b321837/rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94", size = 525766 }, + { url = "https://files.pythonhosted.org/packages/94/3c/1ff1ed6ae323b3e16fdfcdae0f0a67f373a6c3d991229dc32b499edeffb7/rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee", size = 199174 }, + { url = "https://files.pythonhosted.org/packages/ec/ba/5762c0aee2403dfea14ed74b0f8a2415cfdbb21cf745d600d9a8ac952c5b/rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399", size = 213543 }, + { url = "https://files.pythonhosted.org/packages/ab/2a/191374c52d7be0b056cc2a04d718d2244c152f915d4a8d2db2aacc526189/rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489", size = 318369 }, + { url = "https://files.pythonhosted.org/packages/0e/6a/2c9fdcc6d235ac0d61ec4fd9981184689c3e682abd05e3caa49bccb9c298/rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318", size = 311303 }, + { url = "https://files.pythonhosted.org/packages/d2/b2/725487d29633f64ef8f9cbf4729111a0b61702c8f8e94db1653930f52cce/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db", size = 366424 }, + { url = "https://files.pythonhosted.org/packages/7a/8c/668195ab9226d01b7cf7cd9e59c1c0be1df05d602df7ec0cf46f857dcf59/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5", size = 368359 }, + { url = "https://files.pythonhosted.org/packages/52/28/356f6a39c1adeb02cf3e5dd526f5e8e54e17899bef045397abcfbf50dffa/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5", size = 394886 }, + { url = "https://files.pythonhosted.org/packages/a2/65/640fb1a89080a8fb6f4bebd3dafb65a2edba82e2e44c33e6eb0f3e7956f1/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6", size = 432416 }, + { url = "https://files.pythonhosted.org/packages/a7/e8/85835077b782555d6b3416874b702ea6ebd7db1f145283c9252968670dd5/rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209", size = 354819 }, + { url = "https://files.pythonhosted.org/packages/4f/87/1ac631e923d65cbf36fbcfc6eaa702a169496de1311e54be142f178e53ee/rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3", size = 373282 }, + { url = "https://files.pythonhosted.org/packages/e4/ce/cb316f7970189e217b998191c7cf0da2ede3d5437932c86a7210dc1e9994/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272", size = 541540 }, + { url = "https://files.pythonhosted.org/packages/90/d7/4112d7655ec8aff168ecc91d4ceb51c557336edde7e6ccf6463691a2f253/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad", size = 547640 }, + { url = "https://files.pythonhosted.org/packages/ab/44/4f61d64dfed98cc71623f3a7fcb612df636a208b4b2c6611eaa985e130a9/rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58", size = 525555 }, + { url = "https://files.pythonhosted.org/packages/35/f2/a862d81eacb21f340d584cd1c749c289979f9a60e9229f78bffc0418a199/rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0", size = 199338 }, + { url = "https://files.pythonhosted.org/packages/cc/ec/77d0674f9af4872919f3738018558dd9d37ad3f7ad792d062eadd4af7cba/rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c", size = 213585 }, + { url = "https://files.pythonhosted.org/packages/89/b7/f9682c5cc37fcc035f4a0fc33c1fe92ec9cbfdee0cdfd071cf948f53e0df/rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6", size = 321468 }, + { url = "https://files.pythonhosted.org/packages/b8/ad/fc82be4eaceb8d444cb6fc1956ce972b3a0795104279de05e0e4131d0a47/rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b", size = 313062 }, + { url = "https://files.pythonhosted.org/packages/0e/1c/6039e80b13a08569a304dc13476dc986352dca4598e909384db043b4e2bb/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739", size = 370168 }, + { url = "https://files.pythonhosted.org/packages/dc/c9/5b9aa35acfb58946b4b785bc8e700ac313669e02fb100f3efa6176a83e81/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c", size = 371376 }, + { url = "https://files.pythonhosted.org/packages/7b/dd/0e0dbeb70d8a5357d2814764d467ded98d81d90d3570de4fb05ec7224f6b/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee", size = 397200 }, + { url = "https://files.pythonhosted.org/packages/e4/da/a47d931eb688ccfd77a7389e45935c79c41e8098d984d87335004baccb1d/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96", size = 426824 }, + { url = "https://files.pythonhosted.org/packages/0f/f7/a59a673594e6c2ff2dbc44b00fd4ecdec2fc399bb6a7bd82d612699a0121/rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4", size = 357967 }, + { url = "https://files.pythonhosted.org/packages/5f/61/3ba1905396b2cb7088f9503a460b87da33452da54d478cb9241f6ad16d00/rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef", size = 378905 }, + { url = "https://files.pythonhosted.org/packages/08/31/6d0df9356b4edb0a3a077f1ef714e25ad21f9f5382fc490c2383691885ea/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821", size = 546348 }, + { url = "https://files.pythonhosted.org/packages/ae/15/d33c021de5cb793101df9961c3c746dfc476953dbbf5db337d8010dffd4e/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940", size = 553152 }, + { url = "https://files.pythonhosted.org/packages/70/2d/5536d28c507a4679179ab15aa0049440e4d3dd6752050fa0843ed11e9354/rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174", size = 528807 }, + { url = "https://files.pythonhosted.org/packages/e3/62/7ebe6ec0d3dd6130921f8cffb7e34afb7f71b3819aa0446a24c5e81245ec/rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139", size = 200993 }, + { url = "https://files.pythonhosted.org/packages/ec/2f/b938864d66b86a6e4acadefdc56de75ef56f7cafdfd568a6464605457bd5/rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585", size = 214458 }, + { url = "https://files.pythonhosted.org/packages/99/32/43b919a0a423c270a838ac2726b1c7168b946f2563fd99a51aaa9692d00f/rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29", size = 321465 }, + { url = "https://files.pythonhosted.org/packages/58/a9/c4d899cb28e9e47b0ff12462e8f827381f243176036f17bef9c1604667f2/rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91", size = 312900 }, + { url = "https://files.pythonhosted.org/packages/8f/90/9e51670575b5dfaa8c823369ef7d943087bfb73d4f124a99ad6ef19a2b26/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24", size = 370973 }, + { url = "https://files.pythonhosted.org/packages/fc/c1/523f2a03f853fc0d4c1acbef161747e9ab7df0a8abf6236106e333540921/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7", size = 370890 }, + { url = "https://files.pythonhosted.org/packages/51/ca/2458a771f16b0931de4d384decbe43016710bc948036c8f4562d6e063437/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9", size = 397174 }, + { url = "https://files.pythonhosted.org/packages/00/7d/6e06807f6305ea2408b364efb0eef83a6e21b5e7b5267ad6b473b9a7e416/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8", size = 426449 }, + { url = "https://files.pythonhosted.org/packages/8c/d1/6c9e65260a819a1714510a7d69ac1d68aa23ee9ce8a2d9da12187263c8fc/rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879", size = 357698 }, + { url = "https://files.pythonhosted.org/packages/5d/fb/ecea8b5286d2f03eec922be7173a03ed17278944f7c124348f535116db15/rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f", size = 378530 }, + { url = "https://files.pythonhosted.org/packages/e3/e3/ac72f858957f52a109c588589b73bd2fad4a0fc82387fb55fb34aeb0f9cd/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c", size = 545753 }, + { url = "https://files.pythonhosted.org/packages/b2/a4/a27683b519d5fc98e4390a3b130117d80fd475c67aeda8aac83c0e8e326a/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2", size = 552443 }, + { url = "https://files.pythonhosted.org/packages/a1/ed/c074d248409b4432b1ccb2056974175fa0af2d1bc1f9c21121f80a358fa3/rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57", size = 528380 }, + { url = "https://files.pythonhosted.org/packages/d5/bd/04caf938895d2d78201e89c0c8a94dfd9990c34a19ff52fb01d0912343e3/rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a", size = 200540 }, + { url = "https://files.pythonhosted.org/packages/95/cc/109eb8b9863680411ae703664abacaa035820c7755acc9686d5dd02cdd2e/rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2", size = 214111 }, + { url = "https://files.pythonhosted.org/packages/a1/55/228f6d9a8c6940c8d5e49db5e0434ffcbad669c33509ac39cb0af061b0fa/rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22", size = 319496 }, + { url = "https://files.pythonhosted.org/packages/68/61/074236253586feb550954f8b4359d38eefb45bafcbbb7d2e74062a82f386/rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789", size = 311837 }, + { url = "https://files.pythonhosted.org/packages/03/67/ed6c2fe076bf78296934d4356145fedf3c7c2f8d490e099bcf6f31794dc0/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5", size = 367819 }, + { url = "https://files.pythonhosted.org/packages/30/25/4a9e7b89b6760ac032f375cb236e4f8e518ad1fad685c40b6a9752056d6f/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2", size = 368322 }, + { url = "https://files.pythonhosted.org/packages/67/17/0255bb0e564517b53343ea672ebec9fb7ad40e9083ca09a4080fbc986bb9/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c", size = 395552 }, + { url = "https://files.pythonhosted.org/packages/af/6e/77c65ccb0d7cdc39ec2be19b918a4d4fe9e2d2a1c5cab36745b36f2c1e59/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de", size = 433735 }, + { url = "https://files.pythonhosted.org/packages/04/d8/e73d56b1908a6c0e3e5982365eb293170cd458cc25a19363f69c76e00fd2/rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda", size = 355542 }, + { url = "https://files.pythonhosted.org/packages/47/df/e72c79053b0c882b818bfd8f0ed1f1ace550bc9cdba27165cb73dddb9394/rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580", size = 373644 }, + { url = "https://files.pythonhosted.org/packages/7f/00/3e16cb08c0cc6a233f0f61e4d009e3098cbe280ec975d14f28935bd15316/rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b", size = 543139 }, + { url = "https://files.pythonhosted.org/packages/41/71/799c6b6f6031ed535f22fcf6802601cc7f981842bd28007bb7bb4bd10b2f/rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420", size = 548007 }, + { url = "https://files.pythonhosted.org/packages/53/58/ad03eb6718e814fa045198c72d45d2ae60180eb48338f22c9fa34bd89964/rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b", size = 526102 }, + { url = "https://files.pythonhosted.org/packages/78/99/a52e5b460f2311fc8ee75ff769e8d67e76208947180eacb4f153af2d9967/rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7", size = 199391 }, + { url = "https://files.pythonhosted.org/packages/0c/7d/fd42a27fe392a69faf4a5e635870fc425edcb998485ee73afbc734ecef16/rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364", size = 213205 }, + { url = "https://files.pythonhosted.org/packages/06/39/bf1f664c347c946ef56cecaa896e3693d91acc741afa78ebb3fdb7aba08b/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045", size = 319444 }, + { url = "https://files.pythonhosted.org/packages/c1/71/876135d3cb90d62468540b84e8e83ff4dc92052ab309bfdea7ea0b9221ad/rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc", size = 311699 }, + { url = "https://files.pythonhosted.org/packages/f7/da/8ccaeba6a3dda7467aebaf893de9eafd56275e2c90773c83bf15fb0b8374/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02", size = 367825 }, + { url = "https://files.pythonhosted.org/packages/04/b6/02a54c47c178d180395b3c9a8bfb3b93906e08f9acf7b4a1067d27c3fae0/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92", size = 369046 }, + { url = "https://files.pythonhosted.org/packages/a7/64/df4966743aa4def8727dc13d06527c8b13eb7412c1429def2d4701bee520/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d", size = 395896 }, + { url = "https://files.pythonhosted.org/packages/6f/d9/7ff03ff3642c600f27ff94512bb158a8d815fea5ed4162c75a7e850d6003/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855", size = 432427 }, + { url = "https://files.pythonhosted.org/packages/b8/c6/e1b886f7277b3454e55e85332e165091c19114eecb5377b88d892fd36ccf/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511", size = 355403 }, + { url = "https://files.pythonhosted.org/packages/e2/62/e26bd5b944e547c7bfd0b6ca7e306bfa430f8bd298ab72a1217976a7ca8d/rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51", size = 374491 }, + { url = "https://files.pythonhosted.org/packages/c3/92/93c5a530898d3a5d1ce087455071ba714b77806ed9ffee4070d0c7a53b7e/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075", size = 543622 }, + { url = "https://files.pythonhosted.org/packages/01/9e/d68fba289625b5d3c9d1925825d7da716fbf812bda2133ac409021d5db13/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60", size = 548558 }, + { url = "https://files.pythonhosted.org/packages/bf/d6/4b2fad4898154365f0f2bd72ffd190349274a4c1d6a6f94f02a83bb2b8f1/rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344", size = 525753 }, + { url = "https://files.pythonhosted.org/packages/d2/ea/6f121d1802f3adae1981aea4209ea66f9d3c7f2f6d6b85ef4f13a61d17ef/rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989", size = 213529 }, + { url = "https://files.pythonhosted.org/packages/0a/6f/7ab47005469f0d73dad89d29b733e3555d454a45146c30f5628242e56d33/rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e", size = 320800 }, + { url = "https://files.pythonhosted.org/packages/cc/a1/bef9e0ef30f89c7516559ca7acc40e8ae70397535a0b1a4535a4a01d9ed0/rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c", size = 312001 }, + { url = "https://files.pythonhosted.org/packages/31/44/9093c5dca95ee463c3669651e710af182eb6f9cd83626b15a2ebde2247b1/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03", size = 369279 }, + { url = "https://files.pythonhosted.org/packages/6f/ac/0c36e067681fa3fe4c60a9422b011ec0ccc80c1e124f5210951f7982e887/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921", size = 369716 }, + { url = "https://files.pythonhosted.org/packages/6b/78/8896e08625d46ea5bfdd526ee688b91eeafecbc3cf7223612c82ed77905b/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab", size = 396708 }, + { url = "https://files.pythonhosted.org/packages/24/5f/d865ae460e47e46fd2b489f2aceed34439bd8f18a1ff414c299142e0e22a/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5", size = 433356 }, + { url = "https://files.pythonhosted.org/packages/bd/8b/04031937ffa565021f934a9acf44bb6b1b60ea19fa9e58950b32357e85a1/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f", size = 356157 }, + { url = "https://files.pythonhosted.org/packages/3a/64/1f0471b1e688704a716e07340b85f4145109359951feb08676a1f3b8cec4/rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1", size = 374826 }, + { url = "https://files.pythonhosted.org/packages/73/4e/082c0c5eba463e29dff1c6b49557f6ad0d6faae4b46832fa9c1e5b69b7ba/rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074", size = 544549 }, + { url = "https://files.pythonhosted.org/packages/cd/ee/f4af0a62d1ba912c4a3a7f5ec04350946ddd59017f3f3d1c227b20ddf558/rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08", size = 549245 }, + { url = "https://files.pythonhosted.org/packages/59/42/34601dc773be86a85a9ca47f68301a69fdb019aaae0c1426813f265f5ac0/rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec", size = 526722 }, + { url = "https://files.pythonhosted.org/packages/ff/4f/280745d5180c9d78df6b53b6e8b65336f8b6adeb958a8fd19c749fded637/rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8", size = 214379 }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.18.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ruamel-yaml-clib", marker = "python_full_version < '3.13' and platform_python_implementation == 'CPython'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/81/4dfc17eb6ebb1aac314a3eb863c1325b907863a1b8b1382cdffcb6ac0ed9/ruamel.yaml-0.18.6.tar.gz", hash = "sha256:8b27e6a217e786c6fbe5634d8f3f11bc63e0f80f6a5890f28863d9c45aac311b", size = 143362 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/67/8ece580cc363331d9a53055130f86b096bf16e38156e33b1d3014fffda6b/ruamel.yaml-0.18.6-py3-none-any.whl", hash = "sha256:57b53ba33def16c4f3d807c0ccbc00f8a6081827e81ba2491691b76882d0c636", size = 117761 }, +] + +[[package]] +name = "ruamel-yaml-clib" +version = "0.2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/ab/bab9eb1566cd16f060b54055dd39cf6a34bfa0240c53a7218c43e974295b/ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512", size = 213824 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/01/37ac131614f71b98e9b148b2d7790662dcee92217d2fb4bac1aa377def33/ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d", size = 148236 }, + { url = "https://files.pythonhosted.org/packages/61/ee/4874c9fc96010fce85abefdcbe770650c5324288e988d7a48b527a423815/ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462", size = 133996 }, + { url = "https://files.pythonhosted.org/packages/d3/62/c60b034d9a008bbd566eeecf53a5a4c73d191c8de261290db6761802b72d/ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412", size = 526680 }, + { url = "https://files.pythonhosted.org/packages/90/8c/6cdb44f548b29eb6328b9e7e175696336bc856de2ff82e5776f860f03822/ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f", size = 605853 }, + { url = "https://files.pythonhosted.org/packages/88/30/fc45b45d5eaf2ff36cffd215a2f85e9b90ac04e70b97fd4097017abfb567/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334", size = 655206 }, + { url = "https://files.pythonhosted.org/packages/af/dc/133547f90f744a0c827bac5411d84d4e81da640deb3af1459e38c5f3b6a0/ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d", size = 689649 }, + { url = "https://files.pythonhosted.org/packages/23/1d/589139191b187a3c750ae8d983c42fd799246d5f0dd84451a0575c9bdbe9/ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d", size = 100044 }, + { url = "https://files.pythonhosted.org/packages/4f/5b/744df20285a75ac4c606452ce9a0fcc42087d122f42294518ded1017697c/ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31", size = 117825 }, + { url = "https://files.pythonhosted.org/packages/b1/15/971b385c098e8d0d170893f5ba558452bb7b776a0c90658b8f4dd0e3382b/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069", size = 148870 }, + { url = "https://files.pythonhosted.org/packages/01/b0/4ddef56e9f703d7909febc3a421d709a3482cda25826816ec595b73e3847/ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248", size = 134475 }, + { url = "https://files.pythonhosted.org/packages/a4/f7/22d6b620ed895a05d40802d8281eff924dc6190f682d933d4efff60db3b5/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b", size = 544020 }, + { url = "https://files.pythonhosted.org/packages/7c/e4/0d19d65e340f93df1c47f323d95fa4b256bb28320290f5fddef90837853a/ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe", size = 642643 }, + { url = "https://files.pythonhosted.org/packages/c9/ff/f781eb5e2ae011e586d5426e2086a011cf1e0f59704a6cad1387975c5a62/ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899", size = 695832 }, + { url = "https://files.pythonhosted.org/packages/e3/41/f62e67ac651358b8f0d60cfb12ab2daf99b1b69eeaa188d0cec809d943a6/ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9", size = 730923 }, + { url = "https://files.pythonhosted.org/packages/9f/f0/19ab8acbf983cd1b37f47d27ceb8b10a738d60d36316a54bad57e0d73fbb/ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7", size = 99999 }, + { url = "https://files.pythonhosted.org/packages/ec/54/d8a795997921d87224c65d44499ca595a833093fb215b133f920c1062956/ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb", size = 118008 }, + { url = "https://files.pythonhosted.org/packages/7a/a2/eb5e9d088cb9d15c24d956944c09dca0a89108ad6e2e913c099ef36e3f0d/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1", size = 144636 }, + { url = "https://files.pythonhosted.org/packages/66/98/8de4f22bbfd9135deb3422e96d450c4bc0a57d38c25976119307d2efe0aa/ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2", size = 135684 }, + { url = "https://files.pythonhosted.org/packages/30/d3/5fe978cd01a61c12efd24d65fa68c6f28f28c8073a06cf11db3a854390ca/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92", size = 734571 }, + { url = "https://files.pythonhosted.org/packages/55/b3/e2531a050758b717c969cbf76c103b75d8a01e11af931b94ba656117fbe9/ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62", size = 643946 }, + { url = "https://files.pythonhosted.org/packages/0d/aa/06db7ca0995b513538402e11280282c615b5ae5f09eb820460d35fb69715/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9", size = 692169 }, + { url = "https://files.pythonhosted.org/packages/27/38/4cf4d482b84ecdf51efae6635cc5483a83cf5ca9d9c13e205a750e251696/ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d", size = 740325 }, + { url = "https://files.pythonhosted.org/packages/6f/67/c62c6eea53a4feb042727a3d6c18f50dc99683c2b199c06bd2a9e3db8e22/ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa", size = 98639 }, + { url = "https://files.pythonhosted.org/packages/10/d2/52a3d810d0b5b3720725c0504a27b3fced7b6f310fe928f7019d79387bc1/ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b", size = 115305 }, + { url = "https://files.pythonhosted.org/packages/56/a9/e3be88fcebe04016c57207260f2b07c5ecacab86e9f585d10daaa2a4074f/ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001", size = 148719 }, + { url = "https://files.pythonhosted.org/packages/b2/ed/f221e60a4cdc7996aae23643da44b12ef33f457c2a52d590236a6950ac8e/ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf", size = 134394 }, + { url = "https://files.pythonhosted.org/packages/57/e4/f572d7e2502854f15291dfa94eebdc687e04db387559f026995c7697af34/ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c", size = 608071 }, + { url = "https://files.pythonhosted.org/packages/7c/b2/389b345a60131593028b0263fddaa580edb4081697a3f3aa1f168f67519f/ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5", size = 562085 }, + { url = "https://files.pythonhosted.org/packages/8d/c0/fd7196ca7a1c3867e7068ad1c4ff9230291af3f8adab2f9c2c202ecaf9cb/ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b", size = 658185 }, + { url = "https://files.pythonhosted.org/packages/54/61/c18d378caadac66fa97da5d28758c751730dac7510b6a8b8b096da3fff9a/ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880", size = 692222 }, + { url = "https://files.pythonhosted.org/packages/68/4c/f55fbf8510d087449b21b4cde4c05726c8dda5f9b25a8fad06d0c4319249/ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5", size = 100563 }, + { url = "https://files.pythonhosted.org/packages/74/82/e9bb3a3a2268987b7bc472c5c26b420757e04db0d0408e6626d07e388e4c/ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", size = 118400 }, +] + +[[package]] +name = "six" +version = "1.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, +] + +[[package]] +name = "tomli" +version = "2.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/3f/d7af728f075fb08564c5949a9c95e44352e23dee646869fa104a3b2060a3/tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f", size = 15164 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", size = 12757 }, +] + +[[package]] +name = "tornado" +version = "6.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/66/398ac7167f1c7835406888a386f6d0d26ee5dbf197d8a571300be57662d3/tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9", size = 500623 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/d9/c33be3c1a7564f7d42d87a8d186371a75fd142097076767a5c27da941fef/tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8", size = 435924 }, + { url = "https://files.pythonhosted.org/packages/2e/0f/721e113a2fac2f1d7d124b3279a1da4c77622e104084f56119875019ffab/tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14", size = 433883 }, + { url = "https://files.pythonhosted.org/packages/13/cf/786b8f1e6fe1c7c675e79657448178ad65e41c1c9765ef82e7f6f765c4c5/tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4", size = 437224 }, + { url = "https://files.pythonhosted.org/packages/e4/8e/a6ce4b8d5935558828b0f30f3afcb2d980566718837b3365d98e34f6067e/tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842", size = 436597 }, + { url = "https://files.pythonhosted.org/packages/22/d4/54f9d12668b58336bd30defe0307e6c61589a3e687b05c366f804b7faaf0/tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3", size = 436797 }, + { url = "https://files.pythonhosted.org/packages/cf/3f/2c792e7afa7dd8b24fad7a2ed3c2f24a5ec5110c7b43a64cb6095cc106b8/tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f", size = 437516 }, + { url = "https://files.pythonhosted.org/packages/71/63/c8fc62745e669ac9009044b889fc531b6f88ac0f5f183cac79eaa950bb23/tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4", size = 436958 }, + { url = "https://files.pythonhosted.org/packages/94/d4/f8ac1f5bd22c15fad3b527e025ce219bd526acdbd903f52053df2baecc8b/tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698", size = 436882 }, + { url = "https://files.pythonhosted.org/packages/4b/3e/a8124c21cc0bbf144d7903d2a0cadab15cadaf683fa39a0f92bc567f0d4d/tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d", size = 438092 }, + { url = "https://files.pythonhosted.org/packages/d9/2f/3f2f05e84a7aff787a96d5fb06821323feb370fe0baed4db6ea7b1088f32/tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", size = 438532 }, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/79/72064e6a701c2183016abbbfedaba506d81e30e232a68c9f0d6f6fcd1574/traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7", size = 161621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "zipp" +version = "3.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/8b/1239a3ef43a0d0ebdca623fb6413bc7702c321400c5fdd574f0b7aa0fbb4/zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b", size = 23848 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/9e/c96f7a4cd0bf5625bb409b7e61e99b1130dc63a98cb8b24aeabae62d43e8/zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064", size = 8988 }, +] From 59d866f1c777728dbda983b421e15b2ab543ac1c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 13:19:12 -0400 Subject: [PATCH 002/119] feat: update brand.spec.yml --- spec/brand.spec.yml | 108 ++++++++++++++++++++++------------- spec/get-spec-from-quarto.py | 10 +++- 2 files changed, 76 insertions(+), 42 deletions(-) diff --git a/spec/brand.spec.yml b/spec/brand.spec.yml index 20261259..76e1e4ea 100644 --- a/spec/brand.spec.yml +++ b/spec/brand.spec.yml @@ -102,11 +102,10 @@ - id: brand-font description: Font files and definitions for the brand. - arrayOf: - anyOf: - - ref: brand-font-google - - ref: brand-font-file - - ref: brand-font-family + anyOf: + - ref: brand-font-google + - ref: brand-font-file + - ref: brand-font-family - id: brand-font-family description: > @@ -130,7 +129,23 @@ The font files to include. These can be local or online. Local file paths should be relative to the `brand.yml` file. Online paths should be complete URLs. - + weight: + description: The font weights to include. + maybeArrayOf: + ref: brand-font-weight + default: [400, 700] + style: + description: The font styles to include. + maybeArrayOf: + ref: brand-font-style + default: [normal, italic] + display: + description: > + The font display method, determines how a font face is font face is shown depending + on its download status and readiness for use. + enum: [auto, block, swap, fallback, optional] + default: swap + required: [files] - id: brand-font-google description: A Google Font definition. object: @@ -152,7 +167,7 @@ ref: brand-font-weight default: [400, 700] style: - description: The font style to include. + description: The font styles to include. maybeArrayOf: ref: brand-font-style default: [normal, italic] @@ -162,7 +177,7 @@ is shown depending on its download status and readiness for use. enum: [auto, block, swap, fallback, optional] default: swap - + required: [google] - id: brand-font-style description: A font style. enum: [normal, italic] @@ -173,39 +188,41 @@ enum: [100, 200, 300, 400, 500, 600, 700, 800, 900] default: 400 +- id: brand-font-with + description: Font files and definitions for the brand. + object: + closed: false + - id: brand-logo description: > Provide definitions and defaults for brand's logo in various formats and sizes. - anyOf: - - string - - object: - closed: true - properties: - with: - schema: - object: - additionalProperties: - schema: - ref: brand-string-light-dark - small: - description: > - A link or path to the brand's small-sized logo or icon, or a link or path - to both the light and dark versions. - schema: - ref: brand-string-light-dark - medium: - description: > - A link or path to the brand's medium-sized logo, or a link or path - to both the light and dark versions. - schema: - ref: brand-string-light-dark - large: - description: > - A link or path to the brand's large- or full-sized logo, or a link or - path - to both the light and dark versions. - schema: - ref: brand-string-light-dark + object: + closed: true + properties: + with: + schema: + object: + additionalProperties: + schema: + ref: brand-string-light-dark + small: + description: > + A link or path to the brand's small-sized logo or icon, or a link or path + to both the light and dark versions. + schema: + ref: brand-string-light-dark + medium: + description: > + A link or path to the brand's medium-sized logo, or a link or path + to both the light and dark versions. + schema: + ref: brand-string-light-dark + large: + description: > + A link or path to the brand's large- or full-sized logo, or a link or path + to both the light and dark versions. + schema: + ref: brand-string-light-dark - id: brand-maybe-named-color description: > @@ -262,6 +279,12 @@ string: description: The brand's Facebook URL. +- id: brand-named-font + description: Names of customizeable fonts + enum: [base, headings, monospace] +- id: brand-named-logo + description: Names of customizeable logos + enum: [small, medium, large] - id: brand-named-theme-color description: > A named brand color, taken either from `color.theme` or `color.palette` (in that @@ -290,7 +313,7 @@ properties: with: description: Font files and definitions for the brand. - ref: brand-font + ref: brand-font-with base: description: > The base font settings for the brand. These are used as the default for @@ -347,6 +370,11 @@ ref: brand-maybe-named-color background-color: ref: brand-maybe-named-color + files: + maybeArrayOf: + anyOf: [path, string] + description: > + Resolved local paths. - id: brand-typography-options-no-size description: Typographic options without a font size. @@ -364,3 +392,5 @@ background-color: ref: brand-maybe-named-color +# what's the way to make it open with all values brand-font? +# i'm doing some casting instead diff --git a/spec/get-spec-from-quarto.py b/spec/get-spec-from-quarto.py index 84a41f61..bd12b26b 100644 --- a/spec/get-spec-from-quarto.py +++ b/spec/get-spec-from-quarto.py @@ -5,8 +5,12 @@ yaml = YAML() -def read_spec_from_quarto(branch="main"): - url = f"https://github.com/quarto-dev/quarto-cli/raw/{branch}/src/resources/schema/definitions.yml" +def gh_quarto_cli_raw_url(branch="main"): + return f"https://github.com/quarto-dev/quarto-cli/raw/{branch}" + + +def read_yaml_from_quarto(branch="main"): + url = gh_quarto_cli_raw_url(branch) + "/src/resources/schema/definitions.yml" return yaml.load(requests.get(url).content) @@ -51,6 +55,6 @@ def read_brand_spec(): if __name__ == "__main__": - spec = read_spec_from_quarto() + spec = read_yaml_from_quarto() brand = filter_spec_brand(spec) write_brand_spec(brand) From 7a6575e103273934ddc76893a46be841f418aa8a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 13:20:00 -0400 Subject: [PATCH 003/119] feat: Add `brand.schema.json` --- spec/brand.schema.json | 442 +++++++++++++++++++++++++++++ spec/validate-json-schema.md | 511 ++++++++++++++++++++++++++++++++++ spec/validate-json-schema.qmd | 57 ++++ 3 files changed, 1010 insertions(+) create mode 100644 spec/brand.schema.json create mode 100644 spec/validate-json-schema.md create mode 100644 spec/validate-json-schema.qmd diff --git a/spec/brand.schema.json b/spec/brand.schema.json new file mode 100644 index 00000000..1b8be2bc --- /dev/null +++ b/spec/brand.schema.json @@ -0,0 +1,442 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "BrandMeta": { + "object": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "full": { + "type": "string" + }, + "short": { + "type": "string" + } + } + } + } + ] + }, + "link": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "home": { + "type": "string" + }, + "mastodon": { + "type": "string" + }, + "github": { + "type": "string" + }, + "linkedin": { + "type": "string" + }, + "twitter": { + "type": "string" + }, + "facebook": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + "BrandStringLightDark": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "light": { + "type": "string" + }, + "dark": { + "type": "string" + } + } + } + } + ] + }, + "BrandLogo": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "with": { + "object": { + "properties": {} + } + }, + "small": { + "$ref": "#/$defs/BrandStringLightDark" + }, + "medium": { + "$ref": "#/$defs/BrandStringLightDark" + }, + "large": { + "$ref": "#/$defs/BrandStringLightDark" + } + } + } + } + ] + }, + "BrandColorValue": { + "type": "string" + }, + "BrandColor": { + "object": { + "properties": { + "with": { + "object": { + "properties": {} + } + }, + "foreground": { + "$ref": "#/$defs/BrandColorValue" + }, + "background": { + "$ref": "#/$defs/BrandColorValue" + }, + "primary": { + "$ref": "#/$defs/BrandColorValue" + }, + "secondary": { + "$ref": "#/$defs/BrandColorValue" + }, + "tertiary": { + "$ref": "#/$defs/BrandColorValue" + }, + "success": { + "$ref": "#/$defs/BrandColorValue" + }, + "info": { + "$ref": "#/$defs/BrandColorValue" + }, + "warning": { + "$ref": "#/$defs/BrandColorValue" + }, + "danger": { + "$ref": "#/$defs/BrandColorValue" + }, + "light": { + "$ref": "#/$defs/BrandColorValue" + }, + "dark": { + "$ref": "#/$defs/BrandColorValue" + }, + "emphasis": { + "$ref": "#/$defs/BrandColorValue" + }, + "link": { + "$ref": "#/$defs/BrandColorValue" + } + } + } + }, + "BrandMaybeNamedColor": { + "anyOf": [ + { + "$ref": "#/$defs/BrandNamedThemeColor" + }, + { + "type": "string" + } + ] + }, + "BrandNamedThemeColor": { + "enum": [ + "foreground", + "background", + "primary", + "secondary", + "tertiary", + "success", + "info", + "warning", + "danger", + "light", + "dark", + "emphasis", + "link" + ] + }, + "BrandTypography": { + "object": { + "properties": { + "with": { + "$ref": "#/$defs/BrandFont" + }, + "base": { + "$ref": "#/$defs/BrandTypographyOptions" + }, + "headings": { + "$ref": "#/$defs/BrandTypographyOptionsNoSize" + }, + "monospace": { + "$ref": "#/$defs/BrandTypographyOptions" + }, + "emphasis": { + "object": { + "properties": { + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + }, + "link": { + "object": { + "properties": { + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "decoration": { + "type": "string" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + } + } + } + }, + "BrandTypographyOptions": { + "object": { + "properties": { + "family": { + "type": "string" + }, + "size": { + "type": "string" + }, + "line-height": { + "type": "string" + }, + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "style": { + "$ref": "#/$defs/BrandFontStyle" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + }, + "BrandTypographyOptionsNoSize": { + "object": { + "properties": { + "family": { + "type": "string" + }, + "line-height": { + "type": "string" + }, + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "style": { + "$ref": "#/$defs/BrandFontStyle" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + }, + "BrandFont": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/BrandFontGoogle" + }, + { + "$ref": "#/$defs/BrandFontFile" + }, + { + "$ref": "#/$defs/BrandFontFamily" + } + ] + } + }, + "BrandFontWeight": { + "enum": [ + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900 + ] + }, + "BrandFontStyle": { + "enum": [ + "normal", + "italic" + ] + }, + "BrandFontGoogle": { + "object": { + "properties": { + "google": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "family": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/BrandFontWeight" + } + }, + { + "$ref": "#/$defs/BrandFontWeight" + } + ] + }, + "style": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/BrandFontStyle" + } + }, + { + "$ref": "#/$defs/BrandFontStyle" + } + ] + }, + "display": { + "enum": [ + "auto", + "block", + "swap", + "fallback", + "optional" + ] + } + } + } + } + ] + } + } + } + }, + "BrandFontFile": { + "object": { + "properties": { + "family": { + "type": "string" + }, + "files": { + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + ] + } + } + } + }, + "BrandFontFamily": { + "type": "string" + }, + "Brand": { + "object": { + "properties": { + "meta": { + "$ref": "#/$defs/BrandMeta" + }, + "logo": { + "$ref": "#/$defs/BrandLogo" + }, + "color": { + "$ref": "#/$defs/BrandColor" + }, + "typography": { + "$ref": "#/$defs/BrandTypography" + }, + "defaults": { + "type": "object" + } + } + } + } + }, + "$ref": "#/$defs/Brand" +} \ No newline at end of file diff --git a/spec/validate-json-schema.md b/spec/validate-json-schema.md new file mode 100644 index 00000000..5feb7f03 --- /dev/null +++ b/spec/validate-json-schema.md @@ -0,0 +1,511 @@ + + +``` python +import json +from pathlib import Path + +import jsonschema +from jsonschema import Draft202012Validator + +quarto_local_path = Path("~/work/quarto-dev/quarto-cli") +schema_path = quarto_local_path.expanduser().joinpath("src/resources/schema/json-schemas.json") + +if not schema_path.exists(): + raise FileNotFoundError(f"Path {schema_path} does not exist") + +with open(schema_path, 'r') as schema_file: + schema = json.load(schema_file) +``` + +``` python +def validate_json_schema(schema): + try: + Draft202012Validator.check_schema(schema) + print("Schema is valid according to JSON Schema 2020-12 specification.") + except jsonschema.exceptions.SchemaError as e: + print(f"Schema is invalid: {e}") +``` + +``` python +validate_json_schema(schema) +``` + + Schema is invalid: {'values': [None], 'hidden': True} is not of type 'array' + + Failed validating 'type' in metaschema['allOf'][0]['properties']['$defs']['additionalProperties']['$dynamicRef']['allOf'][1]['properties']['anyOf']['items']['$dynamicRef']['allOf'][3]['properties']['enum']: + {'type': 'array', 'items': True} + + On schema['$defs']['PandocFormatOutputFile']['anyOf'][1]['enum']: + {'values': [None], 'hidden': True} + +``` python +schema["$defs"] = {k: v for k, v in schema["$defs"].items() if k.startswith("Brand")} +# not required but useful for brand-yaml work +# schema["type"] = "object" +# schema["properties"] = {"brand": {"$ref": "#/$defs/Brand"}} +schema["$ref"] = "#/$defs/Brand" + +validate_json_schema(schema) +``` + + Schema is valid according to JSON Schema 2020-12 specification. + +
+ +brand-schema.json + + +``` python +import json +from pathlib import Path + +with Path(".").joinpath("brand.schema.json").open("w") as f: + f.write(json.dumps(schema, indent=2)) + +print(json.dumps(schema, indent=2)) +``` + + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "BrandMeta": { + "object": { + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "full": { + "type": "string" + }, + "short": { + "type": "string" + } + } + } + } + ] + }, + "link": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "home": { + "type": "string" + }, + "mastodon": { + "type": "string" + }, + "github": { + "type": "string" + }, + "linkedin": { + "type": "string" + }, + "twitter": { + "type": "string" + }, + "facebook": { + "type": "string" + } + } + } + } + ] + } + } + } + }, + "BrandStringLightDark": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "light": { + "type": "string" + }, + "dark": { + "type": "string" + } + } + } + } + ] + }, + "BrandLogo": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "with": { + "object": { + "properties": {} + } + }, + "small": { + "$ref": "#/$defs/BrandStringLightDark" + }, + "medium": { + "$ref": "#/$defs/BrandStringLightDark" + }, + "large": { + "$ref": "#/$defs/BrandStringLightDark" + } + } + } + } + ] + }, + "BrandColorValue": { + "type": "string" + }, + "BrandColor": { + "object": { + "properties": { + "with": { + "object": { + "properties": {} + } + }, + "foreground": { + "$ref": "#/$defs/BrandColorValue" + }, + "background": { + "$ref": "#/$defs/BrandColorValue" + }, + "primary": { + "$ref": "#/$defs/BrandColorValue" + }, + "secondary": { + "$ref": "#/$defs/BrandColorValue" + }, + "tertiary": { + "$ref": "#/$defs/BrandColorValue" + }, + "success": { + "$ref": "#/$defs/BrandColorValue" + }, + "info": { + "$ref": "#/$defs/BrandColorValue" + }, + "warning": { + "$ref": "#/$defs/BrandColorValue" + }, + "danger": { + "$ref": "#/$defs/BrandColorValue" + }, + "light": { + "$ref": "#/$defs/BrandColorValue" + }, + "dark": { + "$ref": "#/$defs/BrandColorValue" + }, + "emphasis": { + "$ref": "#/$defs/BrandColorValue" + }, + "link": { + "$ref": "#/$defs/BrandColorValue" + } + } + } + }, + "BrandMaybeNamedColor": { + "anyOf": [ + { + "$ref": "#/$defs/BrandNamedThemeColor" + }, + { + "type": "string" + } + ] + }, + "BrandNamedThemeColor": { + "enum": [ + "foreground", + "background", + "primary", + "secondary", + "tertiary", + "success", + "info", + "warning", + "danger", + "light", + "dark", + "emphasis", + "link" + ] + }, + "BrandTypography": { + "object": { + "properties": { + "with": { + "$ref": "#/$defs/BrandFont" + }, + "base": { + "$ref": "#/$defs/BrandTypographyOptions" + }, + "headings": { + "$ref": "#/$defs/BrandTypographyOptionsNoSize" + }, + "monospace": { + "$ref": "#/$defs/BrandTypographyOptions" + }, + "emphasis": { + "object": { + "properties": { + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + }, + "link": { + "object": { + "properties": { + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "decoration": { + "type": "string" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + } + } + } + }, + "BrandTypographyOptions": { + "object": { + "properties": { + "family": { + "type": "string" + }, + "size": { + "type": "string" + }, + "line-height": { + "type": "string" + }, + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "style": { + "$ref": "#/$defs/BrandFontStyle" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + }, + "BrandTypographyOptionsNoSize": { + "object": { + "properties": { + "family": { + "type": "string" + }, + "line-height": { + "type": "string" + }, + "weight": { + "$ref": "#/$defs/BrandFontWeight" + }, + "style": { + "$ref": "#/$defs/BrandFontStyle" + }, + "color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + }, + "background-color": { + "$ref": "#/$defs/BrandMaybeNamedColor" + } + } + } + }, + "BrandFont": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/$defs/BrandFontGoogle" + }, + { + "$ref": "#/$defs/BrandFontFile" + }, + { + "$ref": "#/$defs/BrandFontFamily" + } + ] + } + }, + "BrandFontWeight": { + "enum": [ + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900 + ] + }, + "BrandFontStyle": { + "enum": [ + "normal", + "italic" + ] + }, + "BrandFontGoogle": { + "object": { + "properties": { + "google": { + "anyOf": [ + { + "type": "string" + }, + { + "object": { + "properties": { + "family": { + "type": "string" + }, + "weight": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/BrandFontWeight" + } + }, + { + "$ref": "#/$defs/BrandFontWeight" + } + ] + }, + "style": { + "anyOf": [ + { + "type": "array", + "items": { + "$ref": "#/$defs/BrandFontStyle" + } + }, + { + "$ref": "#/$defs/BrandFontStyle" + } + ] + }, + "display": { + "enum": [ + "auto", + "block", + "swap", + "fallback", + "optional" + ] + } + } + } + } + ] + } + } + } + }, + "BrandFontFile": { + "object": { + "properties": { + "family": { + "type": "string" + }, + "files": { + "anyOf": [ + { + "type": "array", + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + }, + { + "anyOf": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + } + ] + } + } + } + }, + "BrandFontFamily": { + "type": "string" + }, + "Brand": { + "object": { + "properties": { + "meta": { + "$ref": "#/$defs/BrandMeta" + }, + "logo": { + "$ref": "#/$defs/BrandLogo" + }, + "color": { + "$ref": "#/$defs/BrandColor" + }, + "typography": { + "$ref": "#/$defs/BrandTypography" + }, + "defaults": { + "type": "object" + } + } + } + } + }, + "$ref": "#/$defs/Brand" + } + +
diff --git a/spec/validate-json-schema.qmd b/spec/validate-json-schema.qmd new file mode 100644 index 00000000..87a79658 --- /dev/null +++ b/spec/validate-json-schema.qmd @@ -0,0 +1,57 @@ +--- +format: gfm +--- + +```{python} +import json +from pathlib import Path + +import jsonschema +from jsonschema import Draft202012Validator + +quarto_local_path = Path("~/work/quarto-dev/quarto-cli") +schema_path = quarto_local_path.expanduser().joinpath("src/resources/schema/json-schemas.json") + +if not schema_path.exists(): + raise FileNotFoundError(f"Path {schema_path} does not exist") + +with open(schema_path, 'r') as schema_file: + schema = json.load(schema_file) +``` + +```{python} +def validate_json_schema(schema): + try: + Draft202012Validator.check_schema(schema) + print("Schema is valid according to JSON Schema 2020-12 specification.") + except jsonschema.exceptions.SchemaError as e: + print(f"Schema is invalid: {e}") +``` + +```{python} +validate_json_schema(schema) +``` + +```{python} +schema["$defs"] = {k: v for k, v in schema["$defs"].items() if k.startswith("Brand")} +# not required but useful for brand-yaml work +# schema["type"] = "object" +# schema["properties"] = {"brand": {"$ref": "#/$defs/Brand"}} +schema["$ref"] = "#/$defs/Brand" + +validate_json_schema(schema) +``` + +
brand-schema.json + +```{python} +import json +from pathlib import Path + +with Path(".").joinpath("brand.schema.json").open("w") as f: + f.write(json.dumps(schema, indent=2)) + +print(json.dumps(schema, indent=2)) +``` + +
From 1ed32e7099bc4ea58531f9c3d91d839d389c9773 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 13:20:42 -0400 Subject: [PATCH 004/119] chore(examples): add brand-posit.yml --- examples/brand-posit.yml | 57 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 examples/brand-posit.yml diff --git a/examples/brand-posit.yml b/examples/brand-posit.yml new file mode 100644 index 00000000..adfac5d7 --- /dev/null +++ b/examples/brand-posit.yml @@ -0,0 +1,57 @@ +meta: + name: Posit Software, PBC + link: + home: https://posit.co + guide: https://positpbc.atlassian.net/wiki/x/AQAgBQ + mastodon: https://fosstodon.org/@Posit + linkedin: https://www.linkedin.com/company/posit-software/ + twitter: https://twitter.com/posit_pbc + +logo: + small: posit-icon.png + medium: posit.png + large: posit.svg + +color: + palette: + blue: "#447099" + orange: "#EE6331" + gray: "#404041" + white: "#FFFFFF" + teal: "#419599" + green: "#72994E" + burgundy: "#9A4665" + theme: + foreground: "#151515" + background: "#FFFFFF" + primary: "#447099" + secondary: "#707073" + tertiary: "#C2C2C4" + success: "#72994E" + info: "#419599" + warning: "#EE6331" + danger: "#9A4665" + light: "#FFFFFF" + dark: "#404041" + +typography: + font: + - google: "Open Sans" + - google: "Fira Code" + - google: + family: "Roboto Slab" + weight: 600 + style: normal + display: block + + base: + family: "Open Sans" + line-height: 1.25 + size: 1rem + headings: + family: "Roboto Slab" + color: primary + weight: 600 + monospace: + family: "Fira Code" + size: 0.9em From fd21186322207c59395c1550466ab89a0a517910 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 14:32:49 -0400 Subject: [PATCH 005/119] rename: _meta.py --> _brand_meta.py --- pkg-py/src/brand_yaml/__init__.py | 2 +- pkg-py/src/brand_yaml/{_meta.py => _brand_meta.py} | 0 pkg-py/tests/test_meta.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename pkg-py/src/brand_yaml/{_meta.py => _brand_meta.py} (100%) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index e24ef041..f363a5b8 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -5,7 +5,7 @@ from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML -from ._meta import BrandMeta +from ._brand_meta import BrandMeta yaml = YAML() diff --git a/pkg-py/src/brand_yaml/_meta.py b/pkg-py/src/brand_yaml/_brand_meta.py similarity index 100% rename from pkg-py/src/brand_yaml/_meta.py rename to pkg-py/src/brand_yaml/_brand_meta.py diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index 7f5dee6b..47977b3c 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -1,6 +1,6 @@ import pytest from brand_yaml import read_brand_yaml -from brand_yaml._meta import BrandMeta +from brand_yaml._brand_meta import BrandMeta from pydantic import HttpUrl from utils import path_examples From 2e27ecf519340da0eab6c93657e936e3562c4059 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 14:33:20 -0400 Subject: [PATCH 006/119] feat: BrandLightDark and BrandStringLightDark --- pkg-py/src/brand_yaml/_brand_utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 pkg-py/src/brand_yaml/_brand_utils.py diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py new file mode 100644 index 00000000..bc7f5a90 --- /dev/null +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from pydantic import GenericModel, ConfigDict +from typing import TypeVar + +T = TypeVar('T') # Define a type variable T + +class BrandLightDark(GenericModel[T]): + model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) + + light: T = None + dark: T = None + +class BrandStringLightDark(BrandLightDark[str]): + pass From 34fad228f2df1f8f4f2ebbbf7561ca6a18472556 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 15:48:10 -0400 Subject: [PATCH 007/119] chore(3.9): Use `Union[]` type --- pkg-py/src/brand_yaml/_brand_meta.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_meta.py b/pkg-py/src/brand_yaml/_brand_meta.py index 33f39e41..5c374a6d 100644 --- a/pkg-py/src/brand_yaml/_brand_meta.py +++ b/pkg-py/src/brand_yaml/_brand_meta.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Union + from pydantic import BaseModel, ConfigDict, HttpUrl, Field @@ -11,10 +13,10 @@ class BrandMeta(BaseModel): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) - name: str | BrandMetaName = Field( + name: Union[str, BrandMetaName] = Field( None, examples=["Very Big Corporation of America"] ) - link: HttpUrl | BrandLink = Field( + link: Union[HttpUrl, BrandLink] = Field( None, examples=[ "https://very-big-corp.com", From 6fa19a45a294e1f0832befc71df11f433257ddef Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 17:29:48 -0400 Subject: [PATCH 008/119] chore(python): Add eval-type-backport dependency --- pkg-py/pyproject.toml | 1 + pkg-py/uv.lock | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/pkg-py/pyproject.toml b/pkg-py/pyproject.toml index 117b3fdd..f594b84f 100644 --- a/pkg-py/pyproject.toml +++ b/pkg-py/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "ruamel-yaml>=0.18.6", "pydantic>=2.8.2", "jsonschema>=4.23.0", + "eval-type-backport>=0.2.0", ] [project.optional-dependencies] diff --git a/pkg-py/uv.lock b/pkg-py/uv.lock index 312f1655..314172ad 100644 --- a/pkg-py/uv.lock +++ b/pkg-py/uv.lock @@ -37,6 +37,7 @@ name = "brand-yaml" version = "0.1.0" source = { editable = "." } dependencies = [ + { name = "eval-type-backport" }, { name = "jsonschema" }, { name = "pydantic" }, { name = "ruamel-yaml" }, @@ -54,6 +55,7 @@ quarto = [ [package.metadata] requires-dist = [ + { name = "eval-type-backport", specifier = ">=0.2.0" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "nbclient", marker = "extra == 'quarto'", specifier = ">=0.10.0" }, { name = "nbformat", marker = "extra == 'quarto'", specifier = ">=5.10.4" }, @@ -141,6 +143,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "eval-type-backport" +version = "0.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/ca/1601a9fa588867fe2ab6c19ed4c936929160d08a86597adf61bbd443fe57/eval_type_backport-0.2.0.tar.gz", hash = "sha256:68796cfbc7371ebf923f03bdf7bef415f3ec098aeced24e054b253a0e78f7b37", size = 8977 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ac/ac/aa3d8e0acbcd71140420bc752d7c9779cf3a2a3bb1d7ef30944e38b2cd39/eval_type_backport-0.2.0-py3-none-any.whl", hash = "sha256:ac2f73d30d40c5a30a80b8739a789d6bb5e49fdffa66d7912667e2015d9c9933", size = 5855 }, +] + [[package]] name = "exceptiongroup" version = "1.2.2" From 4007a3b09c0981c1793f1e8e873de0f95ae7b911 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 17:30:00 -0400 Subject: [PATCH 009/119] tests(BrandMeta): Test empty `meta` --- pkg-py/tests/test_meta.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index 47977b3c..cf3cee4c 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from brand_yaml import read_brand_yaml from brand_yaml._brand_meta import BrandMeta @@ -15,6 +17,20 @@ def test_brand_meta(): assert meta.link.home == HttpUrl("https://very-big-corp.com/") +def test_brand_meta_empty(): + meta = BrandMeta() + assert meta.name is None + assert meta.link is None + + meta_empty_name = BrandMeta(link="https://example.com") + assert meta_empty_name.name is None + assert meta_empty_name.link == HttpUrl("https://example.com") + + meta_empty_link = BrandMeta(name="Very Big Corporation of America") + assert meta_empty_link.name == "Very Big Corporation of America" + assert meta_empty_link.link is None + + def test_brand_meta_bad_url(): with pytest.raises(ValueError): BrandMeta( @@ -39,6 +55,7 @@ def test_brand_meta_yaml_full(): assert brand.meta.link.twitter == HttpUrl("https://twitter.com/VeryBigCorp") assert brand.meta.link.facebook == HttpUrl("https://facebook.com/Very-Big-Corp") + def test_brand_meta_yaml_small(): brand = read_brand_yaml(path_examples("brand-meta-small.yml")) From 4afca7368856f0bb0abba1f5d4a16604c2d2a3ba Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 17:32:09 -0400 Subject: [PATCH 010/119] chore(BrandMeta) clean up types --- pkg-py/src/brand_yaml/_brand_meta.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_meta.py b/pkg-py/src/brand_yaml/_brand_meta.py index 5c374a6d..813b9e83 100644 --- a/pkg-py/src/brand_yaml/_brand_meta.py +++ b/pkg-py/src/brand_yaml/_brand_meta.py @@ -1,8 +1,6 @@ from __future__ import annotations -from typing import Union - -from pydantic import BaseModel, ConfigDict, HttpUrl, Field +from pydantic import BaseModel, ConfigDict, Field, HttpUrl class BrandMeta(BaseModel): @@ -13,10 +11,10 @@ class BrandMeta(BaseModel): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) - name: Union[str, BrandMetaName] = Field( + name: str | BrandMetaName = Field( None, examples=["Very Big Corporation of America"] ) - link: Union[HttpUrl, BrandLink] = Field( + link: HttpUrl | BrandMetaLink = Field( None, examples=[ "https://very-big-corp.com", @@ -32,7 +30,7 @@ class BrandMetaName(BaseModel): short: str = Field(None, examples=["VBC"]) -class BrandLink(BaseModel): +class BrandMetaLink(BaseModel): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) home: HttpUrl = Field( From 111221bd068527f4bcb459672c9859cc85998f1d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 30 Aug 2024 17:33:11 -0400 Subject: [PATCH 011/119] feat(BrandLogo)[WIP]: Add brand.logo --- pkg-py/src/brand_yaml/__init__.py | 3 ++- pkg-py/src/brand_yaml/_brand_logo.py | 14 ++++++++++++++ pkg-py/src/brand_yaml/_brand_utils.py | 12 +++++++++--- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 pkg-py/src/brand_yaml/_brand_logo.py diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index f363a5b8..3006f23b 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML +from ._brand_logo import BrandLogo from ._brand_meta import BrandMeta yaml = YAML() @@ -14,7 +15,7 @@ class Brand(BaseModel): model_config = ConfigDict(extra="ignore", revalidate_instances="always") meta: BrandMeta = None - # logo: str | BrandLogo = None + logo: str | BrandLogo = None # color: BrandColor = None # typography: BrandTypography = None # defaults: dict[str, Any] = None diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py new file mode 100644 index 00000000..fec649c9 --- /dev/null +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from typing import Union + +from pydantic import ConfigDict +from ._brand_utils import BrandStringLightDark, BrandWith + +class BrandLogo(BrandWith[Union[str, BrandStringLightDark]]): + model_config = ConfigDict(extra="forbid") + + small: str | BrandStringLightDark = None + medium: str | BrandStringLightDark = None + large: str | BrandStringLightDark = None + diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index bc7f5a90..987e1531 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -1,11 +1,11 @@ from __future__ import annotations -from pydantic import GenericModel, ConfigDict -from typing import TypeVar +from pydantic import BaseModel, ConfigDict +from typing import TypeVar, Generic T = TypeVar('T') # Define a type variable T -class BrandLightDark(GenericModel[T]): +class BrandLightDark(BaseModel, Generic[T]): model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) light: T = None @@ -13,3 +13,9 @@ class BrandLightDark(GenericModel[T]): class BrandStringLightDark(BrandLightDark[str]): pass + + +class BrandWith(BaseModel, Generic[T]): + model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) + + with_: dict[str, T] = None From 022b4409818bfc4bc47d1f585f1fcdbaf255035e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 14:04:35 -0400 Subject: [PATCH 012/119] feat: recursive "with_" replacement at creation time --- pkg-py/src/brand_yaml/_brand_logo.py | 10 ++-- pkg-py/src/brand_yaml/_brand_utils.py | 60 ++++++++++++++++++-- pkg-py/tests/test_utils.py | 82 +++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 pkg-py/tests/test_utils.py diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py index fec649c9..e0bf67ce 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -3,12 +3,12 @@ from typing import Union from pydantic import ConfigDict -from ._brand_utils import BrandStringLightDark, BrandWith +from ._brand_utils import BrandLightDarkString, BrandWith -class BrandLogo(BrandWith[Union[str, BrandStringLightDark]]): +class BrandLogo(BrandWith[Union[str, BrandLightDarkString]]): model_config = ConfigDict(extra="forbid") - small: str | BrandStringLightDark = None - medium: str | BrandStringLightDark = None - large: str | BrandStringLightDark = None + small: str | BrandLightDarkString = None + medium: str | BrandLightDarkString = None + large: str | BrandLightDarkString = None diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index 987e1531..522ee05d 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -1,17 +1,22 @@ from __future__ import annotations +import logging +from copy import deepcopy + from pydantic import BaseModel, ConfigDict -from typing import TypeVar, Generic +from typing import Any, TypeVar, Generic + +T = TypeVar("T") -T = TypeVar('T') # Define a type variable T class BrandLightDark(BaseModel, Generic[T]): - model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) + model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) light: T = None dark: T = None -class BrandStringLightDark(BrandLightDark[str]): + +class BrandLightDarkString(BrandLightDark[str]): pass @@ -19,3 +24,50 @@ class BrandWith(BaseModel, Generic[T]): model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) with_: dict[str, T] = None + + def model_post_init(self, __context: Any) -> None: + if self.with_ is None: + return + + logging.debug("resolving with_ values") + self._replace_with_recursively() + + def _replace_with_recursively(self, items: dict | BaseModel | None = None, level=0): + if level > 50: + logging.error("BrandWith recursion limit reached") + return + + if items is None: + items = self + + if not isinstance(items, (dict, BaseModel)): + return + + if isinstance(items, BaseModel): + items_keys = items.model_fields.keys() + elif hasattr(items, "keys"): + items_keys = items.keys() + + for key in items_keys: + logging.debug(f"checking key {key}") + if key == "with_": + continue + + if isinstance(items, BaseModel): + value = getattr(items, key) + elif isinstance(items, dict): + value = items[key] + + if isinstance(value, str) and value in self.with_: + logging.debug(f"replacing key {key}") + if isinstance(items, BaseModel): + setattr(items, key, deepcopy(self.with_[value])) + elif isinstance(items, dict): + items[key] = deepcopy(self.with_[value]) + elif isinstance(value, (dict, BaseModel)): + logging.debug(f"recursing into {key}") + self._replace_with_recursively(value, level + 1) + else: + logging.debug( + f"skipping {key}, not replaceable (or not a dict or pydantic model)" + ) diff --git a/pkg-py/tests/test_utils.py b/pkg-py/tests/test_utils.py new file mode 100644 index 00000000..f105e869 --- /dev/null +++ b/pkg-py/tests/test_utils.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +import logging +from typing import Union + +from brand_yaml._brand_utils import BrandLightDarkString, BrandWith + +logging.basicConfig(level=logging.DEBUG) + + +def test_brand_with_simple(): + class BrandThing(BrandWith[str]): + small: str = None + medium: str = None + large: str = None + + thing = BrandThing( + with_={"sm": "small", "md": "medium", "lg": "large"}, + small="sm", + medium="md", + large="lg", + ) + + assert thing.small == "small" + assert thing.medium == "medium" + assert thing.large == "large" + + +def test_brand_with_simple_mixed(): + class BrandThing(BrandWith[str]): + small: str = None + medium: str = None + large: str = None + + thing = BrandThing( + with_={"sm": "small", "md": "medium", "lg": "large"}, + small="lg", + medium="middle", + large="LG", + ) + + assert thing.small == "large" + assert thing.medium == "middle" + assert thing.large == "LG" + + +def test_brand_with_dict(): + class BrandThing(BrandWith[str]): + small: dict[str, str] = None + medium: dict[str, str] = None + + thing = BrandThing( + with_={"sm": "small", "md": "medium", "lg": "large"}, + small={"one": "sm", "two": "md"}, + medium={"three": "md"}, + ) + + assert thing.small == {"one": "small", "two": "medium"} + assert thing.medium == {"three": "medium"} + + +def test_brand_with_basemodel(): + class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): + small: Union[str, BrandLightDarkString] = None + medium: Union[str, BrandLightDarkString] = None + + thing = BrandThing( + with_={ + "sm": "small-light", + "md": {"light": "medium-light", "dark": "medium-dark"} + }, + small={"light": "sm", "dark": "small-dark"}, + medium="md", + ) + + assert thing.small.light == "small-light" + assert thing.small.dark == "small-dark" + assert thing.medium.light == "medium-light" + assert thing.medium.dark == "medium-dark" + + assert isinstance(thing.small, BrandLightDarkString) + assert isinstance(thing.medium, BrandLightDarkString) From 3b0fb98322c24fc60eeea26e8e0ec075ddb28af1 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 15:20:41 -0400 Subject: [PATCH 013/119] feat: Support reading data with `with:` and replace in `with` --- pkg-py/src/brand_yaml/_brand_utils.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index 522ee05d..dcea7aa5 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -3,8 +3,8 @@ import logging from copy import deepcopy -from pydantic import BaseModel, ConfigDict -from typing import Any, TypeVar, Generic +from pydantic import BaseModel, ConfigDict, Field +from typing import Any, TypeVar, Generic, Optional T = TypeVar("T") @@ -21,9 +21,13 @@ class BrandLightDarkString(BrandLightDark[str]): class BrandWith(BaseModel, Generic[T]): - model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) + model_config = ConfigDict( + extra="ignore", + str_strip_whitespace=True, + populate_by_name=True, + ) - with_: dict[str, T] = None + with_: Optional[dict[str, T]] = Field(default=None, alias="with") def model_post_init(self, __context: Any) -> None: if self.with_ is None: @@ -50,8 +54,6 @@ def _replace_with_recursively(self, items: dict | BaseModel | None = None, level for key in items_keys: logging.debug(f"checking key {key}") - if key == "with_": - continue if isinstance(items, BaseModel): value = getattr(items, key) From 22e9a0cdf59ef77a5ee16510d9e5b327e6959ed6 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 15:21:05 -0400 Subject: [PATCH 014/119] tests(BrandLogo): Add brand logo tests --- examples/brand-logo-full.yml | 14 +++++++++++ examples/brand-logo-light-dark.yml | 6 +++++ examples/brand-logo-simple.yml | 4 ++++ examples/brand-logo-single.yml | 1 + pkg-py/src/brand_yaml/_brand_logo.py | 22 ++++++++++++++++- pkg-py/tests/test_logo.py | 35 ++++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 examples/brand-logo-full.yml create mode 100644 examples/brand-logo-light-dark.yml create mode 100644 examples/brand-logo-simple.yml create mode 100644 examples/brand-logo-single.yml create mode 100644 pkg-py/tests/test_logo.py diff --git a/examples/brand-logo-full.yml b/examples/brand-logo-full.yml new file mode 100644 index 00000000..e9162bc0 --- /dev/null +++ b/examples/brand-logo-full.yml @@ -0,0 +1,14 @@ +logo: + with: + primary: full-color.png + primary-svg: full-color.svg + reverse: full-color-reverse.png + black: black.png + white: white.png + icon: favicon.png + both: + light: primary + dark: reverse + small: icon + medium: both + large: primary-svg diff --git a/examples/brand-logo-light-dark.yml b/examples/brand-logo-light-dark.yml new file mode 100644 index 00000000..42bfa1a7 --- /dev/null +++ b/examples/brand-logo-light-dark.yml @@ -0,0 +1,6 @@ +logo: + small: icon.png + medium: + light: logo-light.png + dark: logo-dark.png + large: display.svg \ No newline at end of file diff --git a/examples/brand-logo-simple.yml b/examples/brand-logo-simple.yml new file mode 100644 index 00000000..e14083a5 --- /dev/null +++ b/examples/brand-logo-simple.yml @@ -0,0 +1,4 @@ +logo: + small: icon.png + medium: logo.png + large: display.svg \ No newline at end of file diff --git a/examples/brand-logo-single.yml b/examples/brand-logo-single.yml new file mode 100644 index 00000000..8d6b8ddb --- /dev/null +++ b/examples/brand-logo-single.yml @@ -0,0 +1 @@ +logo: posit.png \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py index e0bf67ce..e2e3a1f3 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -6,8 +6,28 @@ from ._brand_utils import BrandLightDarkString, BrandWith class BrandLogo(BrandWith[Union[str, BrandLightDarkString]]): - model_config = ConfigDict(extra="forbid") + """ + Brand Logos + `logo` stores a single brand logo or a set of logos in different sizes and + possibly in different color schemes. + + Attributes: + + small + : A small logo, typically used as an favicon or mobile app icon. + + medium + : A medium-sized logo, typically used in the header of a website. + + large + : A large logo, typically used in a larger format such as a title slide + or in marketing materials. + """ + model_config = ConfigDict(extra="ignore") + + # TODO: Currently we're using a string for the logo path, but we should + # update this to use a validated Path or URL in the future. small: str | BrandLightDarkString = None medium: str | BrandLightDarkString = None large: str | BrandLightDarkString = None diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py new file mode 100644 index 00000000..e7202465 --- /dev/null +++ b/pkg-py/tests/test_logo.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from brand_yaml import read_brand_yaml +from utils import path_examples + +def test_brand_logo_single(): + brand = read_brand_yaml(path_examples("brand-logo-single.yml")) + + assert brand.logo == "posit.png" + +def test_brand_logo_simple(): + brand = read_brand_yaml(path_examples("brand-logo-simple.yml")) + + assert brand.logo.small == "icon.png" + assert brand.logo.medium == "logo.png" + assert brand.logo.large == "display.svg" + +def test_brand_logo_light_dark(): + brand = read_brand_yaml(path_examples("brand-logo-light-dark.yml")) + + assert brand.logo.small == "icon.png" + assert brand.logo.medium.light == "logo-light.png" + assert brand.logo.medium.dark == "logo-dark.png" + assert brand.logo.large == "display.svg" + +def test_brand_logo_full(): + brand_logo_full = read_brand_yaml(path_examples("brand-logo-full.yml")) + + assert brand_logo_full.logo.small == "favicon.png" + + assert brand_logo_full.logo.medium.light == "full-color.png" + assert brand_logo_full.logo.medium.dark == "full-color-reverse.png" + + assert brand_logo_full.logo.large == "full-color.svg" + From ac539ce782fb3fd316fc182ea8f336710e56bd47 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 15:27:38 -0400 Subject: [PATCH 015/119] docs(BrandLogo): fix attributes syntax --- pkg-py/src/brand_yaml/_brand_logo.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py index e2e3a1f3..1a4bd483 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -12,17 +12,19 @@ class BrandLogo(BrandWith[Union[str, BrandLightDarkString]]): `logo` stores a single brand logo or a set of logos in different sizes and possibly in different color schemes. - Attributes: + Attributes + ---------- small - : A small logo, typically used as an favicon or mobile app icon. + A small logo, typically used as an favicon or mobile app icon. medium - : A medium-sized logo, typically used in the header of a website. + A medium-sized logo, typically used in the header of a website. large - : A large logo, typically used in a larger format such as a title slide + A large logo, typically used in a larger format such as a title slide or in marketing materials. + """ model_config = ConfigDict(extra="ignore") From e10150112ace1e1ef6953dad0a3ac78212331081 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 15:27:48 -0400 Subject: [PATCH 016/119] feat(BrandWith): Always revalidate instances The result is that you can post-hoc re-use `with` values. --- pkg-py/src/brand_yaml/_brand_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index dcea7aa5..2d51f32e 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -25,6 +25,7 @@ class BrandWith(BaseModel, Generic[T]): extra="ignore", str_strip_whitespace=True, populate_by_name=True, + revalidate_instances="always", ) with_: Optional[dict[str, T]] = Field(default=None, alias="with") From e7ea7874ddfa9ee45fc582d52973872a61c7bce3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 16:18:59 -0400 Subject: [PATCH 017/119] test(BrandWith): Add more nested `with` tests --- pkg-py/tests/test_utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pkg-py/tests/test_utils.py b/pkg-py/tests/test_utils.py index f105e869..e4ce4d44 100644 --- a/pkg-py/tests/test_utils.py +++ b/pkg-py/tests/test_utils.py @@ -80,3 +80,23 @@ class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): assert isinstance(thing.small, BrandLightDarkString) assert isinstance(thing.medium, BrandLightDarkString) + +def test_brand_with_nested(): + class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): + the: Union[str, BrandLightDarkString] = None + + thing = BrandThing( + with_={ + "light": "the-light", + "dark": "the-dark", + "both": {"light": "the-light", "dark": "the-dark"} + }, + the="both", + ) + + assert thing.the == thing.with_["both"] + assert thing.with_["both"].light == "the-light" + assert thing.with_["both"].dark == "the-dark" + + assert isinstance(thing.with_["both"], BrandLightDarkString) + assert isinstance(thing.the, BrandLightDarkString) From 5dd45c358e0693f6b8ad002ff02a54477be0ecbb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 16:33:42 -0400 Subject: [PATCH 018/119] chore: Add TODO about recursing into nested BrandWith instances --- pkg-py/src/brand_yaml/_brand_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index 2d51f32e..3154f068 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -68,6 +68,7 @@ def _replace_with_recursively(self, items: dict | BaseModel | None = None, level elif isinstance(items, dict): items[key] = deepcopy(self.with_[value]) elif isinstance(value, (dict, BaseModel)): + # TODO: we may want to avoid recursing into child BrandWith instances logging.debug(f"recursing into {key}") self._replace_with_recursively(value, level + 1) else: From 9478b91abbaf6cd16bc2164211b6d6ccacd6af95 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 16:34:01 -0400 Subject: [PATCH 019/119] chore(BrandLogo): forbid extra fields --- pkg-py/src/brand_yaml/_brand_logo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py index 1a4bd483..2a14e01f 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -26,7 +26,7 @@ class BrandLogo(BrandWith[Union[str, BrandLightDarkString]]): or in marketing materials. """ - model_config = ConfigDict(extra="ignore") + model_config = ConfigDict(extra="forbid") # TODO: Currently we're using a string for the logo path, but we should # update this to use a validated Path or URL in the future. From e48b534892793c3be6e4276d20d7f6d008155a2c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 3 Sep 2024 16:55:13 -0400 Subject: [PATCH 020/119] feat(BrandColor): Add brand.color --- examples/brand-color-posit-direct.yml | 12 +++ examples/brand-color-posit-with.yml | 21 +++++ pkg-py/src/brand_yaml/__init__.py | 6 +- pkg-py/src/brand_yaml/_brand_color.py | 120 ++++++++++++++++++++++++++ pkg-py/tests/test_color.py | 45 ++++++++++ 5 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 examples/brand-color-posit-direct.yml create mode 100644 examples/brand-color-posit-with.yml create mode 100644 pkg-py/src/brand_yaml/_brand_color.py create mode 100644 pkg-py/tests/test_color.py diff --git a/examples/brand-color-posit-direct.yml b/examples/brand-color-posit-direct.yml new file mode 100644 index 00000000..b24f1ab2 --- /dev/null +++ b/examples/brand-color-posit-direct.yml @@ -0,0 +1,12 @@ +color: + foreground: "#151515" + background: "#FFFFFF" + primary: "#447099" + secondary: "#707073" + tertiary: "#C2C2C4" + success: "#72994E" + info: "#419599" + warning: "#EE6331" + danger: "#9A4665" + light: "#FFFFFF" + dark: "#404041" \ No newline at end of file diff --git a/examples/brand-color-posit-with.yml b/examples/brand-color-posit-with.yml new file mode 100644 index 00000000..f75c4f1f --- /dev/null +++ b/examples/brand-color-posit-with.yml @@ -0,0 +1,21 @@ +color: + with: + white: "#FFFFFF" + black: "#151515" + blue: "#447099" + orange: "#EE6331" + green: "#72994E" + teal: "#419599" + burgundy: "#9A4665" + + foreground: black + background: white + primary: blue + secondary: "#707073" + tertiary: "#C2C2C4" + success: green + info: teal + warning: orange + danger: burgundy + light: white + dark: "#404041" \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 3006f23b..bb90eb84 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -1,12 +1,14 @@ from __future__ import annotations from pathlib import Path +from typing import Optional, Union from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML from ._brand_logo import BrandLogo from ._brand_meta import BrandMeta +from ._brand_color import BrandColor yaml = YAML() @@ -15,8 +17,8 @@ class Brand(BaseModel): model_config = ConfigDict(extra="ignore", revalidate_instances="always") meta: BrandMeta = None - logo: str | BrandLogo = None - # color: BrandColor = None + logo: Optional[Union[str, BrandLogo]] = None + color: BrandColor = None # typography: BrandTypography = None # defaults: dict[str, Any] = None diff --git a/pkg-py/src/brand_yaml/_brand_color.py b/pkg-py/src/brand_yaml/_brand_color.py new file mode 100644 index 00000000..00a1254d --- /dev/null +++ b/pkg-py/src/brand_yaml/_brand_color.py @@ -0,0 +1,120 @@ +from __future__ import annotations + +from typing import Optional + +from pydantic import ConfigDict, Field + +from ._brand_utils import BrandWith + + +class BrandColor(BrandWith[str]): + """ + Brand Colors + + The brand's custom color palette and theme. + + Attributes + ---------- + + foreground + The foreground color, used for text. + + background + The background color, used for the page or main background. + + primary + The primary accent color, i.e. the main theme color. Typically used for + hyperlinks, active states, primary action buttons, etc. + + secondary + The secondary accent color. Typically used for lighter text or disabled + states. + + tertiary + The tertiary accent color. Typically an even lighter color, used for + hover states, accents, and wells. + + success + The color used for positive or successful actions and information. + + info + The color used for neutral or informational actions and information. + + warning + The color used for warning or cautionary actions and information. + + danger + The color used for errors, dangerous actions, or negative information. + + light + A bright color, used as a high-contrast foreground color on dark + elements or low-contrast background color on light elements. + + dark + A dark color, used as a high-contrast foreground color on light elements + or high-contrast background color on light elements. + + emphasis + A color used to emphasize or highlight text or elements. + + link + The color used for hyperlinks. If not defined, the `primary` color is + used. + + """ + model_config = ConfigDict(extra="forbid") + + foreground: Optional[str] = Field( + default=None, + description="The foreground color, used for text.", + ) + background: Optional[str] = Field( + default=None, + description="The background color, used for the page or main background.", + ) + primary: Optional[str] = Field( + default=None, + description="The primary accent color, i.e. the main theme color. Typically used for hyperlinks, active states, primary action buttons, etc.", + ) + secondary: Optional[str] = Field( + default=None, + description="The secondary accent color. Typically used for lighter text or disabled states.", + ) + tertiary: Optional[str] = Field( + default=None, + description="The tertiary accent color. Typically an even lighter color, used for hover states, accents, and wells.", + ) + + success: Optional[str] = Field( + default=None, + description="The color used for positive or successful actions and information.", + ) + info: Optional[str] = Field( + default=None, + description="The color used for neutral or informational actions and information.", + ) + warning: Optional[str] = Field( + default=None, + description="The color used for warning or cautionary actions and information.", + ) + danger: Optional[str] = Field( + default=None, + description="The color used for errors, dangerous actions, or negative information.", + ) + + light: Optional[str] = Field( + default=None, + description="A bright color, used as a high-contrast foreground color on dark elements or low-contrast background color on light elements.", + ) + dark: Optional[str] = Field( + default=None, + description="A dark color, used as a high-contrast foreground color on light elements or high-contrast background color on light elements.", + ) + emphasis: Optional[str] = Field( + default=None, + description="A color used to emphasize or highlight text or elements.", + ) + link: Optional[str] = Field( + default=None, + description="The color used for hyperlinks. If not defined, the `primary` color is used.", + ) diff --git a/pkg-py/tests/test_color.py b/pkg-py/tests/test_color.py new file mode 100644 index 00000000..c8e4f7c2 --- /dev/null +++ b/pkg-py/tests/test_color.py @@ -0,0 +1,45 @@ +from __future__ import annotations + +from brand_yaml import read_brand_yaml +from utils import path_examples + +def test_brand_color_posit_direct(): + brand = read_brand_yaml(path_examples("brand-color-posit-direct.yml")) + + assert brand.color.foreground == "#151515" + assert brand.color.background == "#FFFFFF" + assert brand.color.primary == "#447099" + assert brand.color.secondary == "#707073" + assert brand.color.tertiary == "#C2C2C4" + assert brand.color.success == "#72994E" + assert brand.color.info == "#419599" + assert brand.color.warning == "#EE6331" + assert brand.color.danger == "#9A4665" + assert brand.color.light == "#FFFFFF" + assert brand.color.dark == "#404041" + +def test_brand_color_posit_with(): + brand = read_brand_yaml(path_examples("brand-color-posit-with.yml")) + + # Same final values as above, but re-uses color definitions from `with` + assert brand.color.foreground == "#151515" + assert brand.color.background == "#FFFFFF" + assert brand.color.primary == "#447099" + assert brand.color.secondary == "#707073" + assert brand.color.tertiary == "#C2C2C4" + assert brand.color.success == "#72994E" + assert brand.color.info == "#419599" + assert brand.color.warning == "#EE6331" + assert brand.color.danger == "#9A4665" + assert brand.color.light == "#FFFFFF" + assert brand.color.dark == "#404041" + + assert brand.color.with_ == { + "white": "#FFFFFF", + "black": "#151515", + "blue": "#447099", + "orange": "#EE6331", + "green": "#72994E", + "teal": "#419599", + "burgundy": "#9A4665" + } From 7fcdce31ddf0f9e18e0455bcd575344cf46618f9 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 4 Sep 2024 13:44:42 -0400 Subject: [PATCH 021/119] fix: whitespace --- pkg-py/src/brand_yaml/_brand_color.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_color.py b/pkg-py/src/brand_yaml/_brand_color.py index 00a1254d..f8eda0d0 100644 --- a/pkg-py/src/brand_yaml/_brand_color.py +++ b/pkg-py/src/brand_yaml/_brand_color.py @@ -18,45 +18,45 @@ class BrandColor(BrandWith[str]): foreground The foreground color, used for text. - + background The background color, used for the page or main background. - + primary The primary accent color, i.e. the main theme color. Typically used for hyperlinks, active states, primary action buttons, etc. - + secondary The secondary accent color. Typically used for lighter text or disabled states. - + tertiary The tertiary accent color. Typically an even lighter color, used for hover states, accents, and wells. - + success The color used for positive or successful actions and information. - + info The color used for neutral or informational actions and information. - + warning The color used for warning or cautionary actions and information. - + danger The color used for errors, dangerous actions, or negative information. - + light A bright color, used as a high-contrast foreground color on dark elements or low-contrast background color on light elements. - + dark A dark color, used as a high-contrast foreground color on light elements or high-contrast background color on light elements. - + emphasis A color used to emphasize or highlight text or elements. - + link The color used for hyperlinks. If not defined, the `primary` color is used. From 362f4be32b6520554201417765126bb40d24be80 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 4 Sep 2024 13:50:29 -0400 Subject: [PATCH 022/119] chore(examples): Full meta.name for Posit --- examples/brand-posit.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/brand-posit.yml b/examples/brand-posit.yml index adfac5d7..7e2cfdd2 100644 --- a/examples/brand-posit.yml +++ b/examples/brand-posit.yml @@ -1,5 +1,7 @@ meta: - name: Posit Software, PBC + name: + full: Posit Software, PBC + short: Posit link: home: https://posit.co guide: https://positpbc.atlassian.net/wiki/x/AQAgBQ From 74573c2c5324c739e65c950862db748558f311e7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 4 Sep 2024 14:24:01 -0400 Subject: [PATCH 023/119] fix: Replace `with` on assignment, too --- pkg-py/src/brand_yaml/_brand_utils.py | 4 ++++ pkg-py/tests/test_logo.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index 3154f068..c174c4bf 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -37,6 +37,10 @@ def model_post_init(self, __context: Any) -> None: logging.debug("resolving with_ values") self._replace_with_recursively() + def __setattr__(self, name: str, value: Any) -> None: + super().__setattr__(name, value) + self._replace_with_recursively() + def _replace_with_recursively(self, items: dict | BaseModel | None = None, level=0): if level > 50: logging.error("BrandWith recursion limit reached") diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index e7202465..6eaaa38a 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -33,3 +33,6 @@ def test_brand_logo_full(): assert brand_logo_full.logo.large == "full-color.svg" + # replace small with new value from "with" + brand_logo_full.logo.small = "black" + assert brand_logo_full.logo.small == "black.png" From 81405e80fd126657255787aa8ab56b4e05cab19b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 4 Sep 2024 14:25:08 -0400 Subject: [PATCH 024/119] chore(BrandLogo): Use generic `BrandLightDark` --- pkg-py/src/brand_yaml/_brand_logo.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py index 2a14e01f..7711edbf 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -3,9 +3,10 @@ from typing import Union from pydantic import ConfigDict -from ._brand_utils import BrandLightDarkString, BrandWith +from ._brand_utils import BrandLightDark, BrandWith -class BrandLogo(BrandWith[Union[str, BrandLightDarkString]]): + +class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): """ Brand Logos @@ -17,20 +18,19 @@ class BrandLogo(BrandWith[Union[str, BrandLightDarkString]]): small A small logo, typically used as an favicon or mobile app icon. - + medium A medium-sized logo, typically used in the header of a website. large A large logo, typically used in a larger format such as a title slide or in marketing materials. - + """ model_config = ConfigDict(extra="forbid") # TODO: Currently we're using a string for the logo path, but we should # update this to use a validated Path or URL in the future. - small: str | BrandLightDarkString = None - medium: str | BrandLightDarkString = None - large: str | BrandLightDarkString = None - + small: str | BrandLightDark[str] = None + medium: str | BrandLightDark[str] = None + large: str | BrandLightDark[str] = None From 2c3d0ec4c8cd1a596b8cc41f0b1f2718337dc887 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 4 Sep 2024 14:25:33 -0400 Subject: [PATCH 025/119] chore: revalidate instances and validate assignment --- pkg-py/src/brand_yaml/__init__.py | 10 +++++++--- pkg-py/src/brand_yaml/_brand_logo.py | 8 +++++++- pkg-py/src/brand_yaml/_brand_utils.py | 1 + 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index bb90eb84..77ef685d 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Optional, Union +from typing import Union from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML @@ -14,10 +14,14 @@ class Brand(BaseModel): - model_config = ConfigDict(extra="ignore", revalidate_instances="always") + model_config = ConfigDict( + extra="ignore", + revalidate_instances="always", + validate_assignment=True, + ) meta: BrandMeta = None - logo: Optional[Union[str, BrandLogo]] = None + logo: Union[str, BrandLogo] = None color: BrandColor = None # typography: BrandTypography = None # defaults: dict[str, Any] = None diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/_brand_logo.py index 7711edbf..1b44954d 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/_brand_logo.py @@ -27,7 +27,13 @@ class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): or in marketing materials. """ - model_config = ConfigDict(extra="forbid") + + model_config = ConfigDict( + extra="forbid", + revalidate_instances="always", + validate_assignment=True, + use_attribute_docstrings=True, + ) # TODO: Currently we're using a string for the logo path, but we should # update this to use a validated Path or URL in the future. diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index c174c4bf..1e06ed46 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -26,6 +26,7 @@ class BrandWith(BaseModel, Generic[T]): str_strip_whitespace=True, populate_by_name=True, revalidate_instances="always", + validate_assignment=True, ) with_: Optional[dict[str, T]] = Field(default=None, alias="with") From dd3a5b863fc3ad72605ac261e0eecce1517ee5ef Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 12:06:02 -0400 Subject: [PATCH 026/119] feat(logging): Add pkg logging --- pkg-py/src/brand_yaml/_brand_utils.py | 4 +--- pkg-py/src/brand_yaml/_utils_logging.py | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 pkg-py/src/brand_yaml/_utils_logging.py diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index 1e06ed46..333e41ee 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -1,10 +1,8 @@ from __future__ import annotations -import logging from copy import deepcopy -from pydantic import BaseModel, ConfigDict, Field -from typing import Any, TypeVar, Generic, Optional +from ._utils_logging import logger T = TypeVar("T") diff --git a/pkg-py/src/brand_yaml/_utils_logging.py b/pkg-py/src/brand_yaml/_utils_logging.py new file mode 100644 index 00000000..8172185a --- /dev/null +++ b/pkg-py/src/brand_yaml/_utils_logging.py @@ -0,0 +1,20 @@ +import logging + +logger = logging.getLogger("brand_yaml") +if len(logger.handlers) == 0: + logger.addHandler(logging.NullHandler()) + +def log_add_console_stream_handler(): + for handler in logger.handlers: + if isinstance(handler, logging.StreamHandler): + return + + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch = logging.StreamHandler() + ch.setFormatter(formatter) + logger.addHandler(ch) + +def log_set_debug(): + log_add_console_stream_handler() + logger.setLevel(logging.DEBUG) + logger.debug("Debug logging enabled") From aa4a13f4dabe4c98deb4f8e9281f29d0bacf1a80 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 12:07:25 -0400 Subject: [PATCH 027/119] feat(BrandWith): Check for circular references, improve recursrive with handling --- pkg-py/src/brand_yaml/_brand_utils.py | 167 ++++++++++++++++++++++---- pkg-py/tests/test_utils.py | 13 +- 2 files changed, 157 insertions(+), 23 deletions(-) diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_brand_utils.py index 333e41ee..070602c6 100644 --- a/pkg-py/src/brand_yaml/_brand_utils.py +++ b/pkg-py/src/brand_yaml/_brand_utils.py @@ -1,6 +1,10 @@ from __future__ import annotations from copy import deepcopy +from textwrap import indent +from typing import Any, Generic, Optional, TypeVar, Union + +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from ._utils_logging import logger @@ -18,6 +22,14 @@ class BrandLightDarkString(BrandLightDark[str]): pass +LeafNode = Union[str, float, int, bool, None] + + +def is_leaf_node(value: Any) -> bool: + # Note: We treat iterables as leaf nodes + return not isinstance(value, (dict, BaseModel)) + + class BrandWith(BaseModel, Generic[T]): model_config = ConfigDict( extra="ignore", @@ -29,20 +41,54 @@ class BrandWith(BaseModel, Generic[T]): with_: Optional[dict[str, T]] = Field(default=None, alias="with") - def model_post_init(self, __context: Any) -> None: + @field_validator("with_", mode="after") + @classmethod + def validate_with_(cls, value: dict[str, T] | None) -> dict[str, T] | None: + if value is None: + return value + + check_circular_references(value, name="with") + return value + + @model_validator(mode="after") + def resolve_with_values(self, __context: Any) -> None: if self.with_ is None: - return + return self - logging.debug("resolving with_ values") + logger.debug("resolving with_ values") self._replace_with_recursively() + return self def __setattr__(self, name: str, value: Any) -> None: super().__setattr__(name, value) - self._replace_with_recursively() + if name != "with_": + self._replace_with_recursively(value) + + def _get_with(self, key: str, level=0) -> object: + """ + Finds `key` in `with_`, which may require recursively resolving nested + values from `with_`. + """ + if key not in self.with_: + return key + + # Note that this is simplified by the fact that we've already confirmed + # that no circular references exist in `with_`. + + with_value = deepcopy(self.with_[key]) + logger.debug( + level_indent(f"key {key} is in with_ with value {with_value!r}", level) + ) + + if is_leaf_node(self.with_[key]): + return with_value + else: + self._replace_with_recursively(with_value, level) + return with_value def _replace_with_recursively(self, items: dict | BaseModel | None = None, level=0): if level > 50: - logging.error("BrandWith recursion limit reached") + logger.error("BrandWith recursion limit reached") return if items is None: @@ -51,30 +97,107 @@ def _replace_with_recursively(self, items: dict | BaseModel | None = None, level if not isinstance(items, (dict, BaseModel)): return - if isinstance(items, BaseModel): - items_keys = items.model_fields.keys() - elif hasattr(items, "keys"): - items_keys = items.keys() - - for key in items_keys: - logging.debug(f"checking key {key}") + for key in item_keys(items): + value = get_value(items, key) - if isinstance(items, BaseModel): - value = getattr(items, key) - elif isinstance(items, dict): - value = items[key] + if value is self.with_: + # We replace `with_` when resolving sibling fields + continue + logger.debug(level_indent(f"inspecting key {key}", level)) if isinstance(value, str) and value in self.with_: - logging.debug(f"replacing key {key}") + new_value = self._get_with(value, level + 1) + logger.debug( + level_indent( + f"replacing key {key} with value from {value}: {new_value!r}", + level, + ) + ) if isinstance(items, BaseModel): - setattr(items, key, deepcopy(self.with_[value])) + setattr(items, key, new_value) elif isinstance(items, dict): - items[key] = deepcopy(self.with_[value]) + items[key] = new_value elif isinstance(value, (dict, BaseModel)): # TODO: we may want to avoid recursing into child BrandWith instances - logging.debug(f"recursing into {key}") + logger.debug(level_indent(f"recursing into {key}", level)) self._replace_with_recursively(value, level + 1) else: - logging.debug( - f"skipping {key}, not replaceable (or not a dict or pydantic model)" + logger.debug( + level_indent( + f"skipping {key}, not replaceable (or not a dict or pydantic model)", + level, + ) ) + + +def level_indent(x: str, level: int) -> str: + return indent(x, ("." * level)) + + +def item_keys(item: dict | BaseModel) -> list[str]: + if isinstance(item, BaseModel): + return item.model_fields.keys() + elif hasattr(item, "keys"): + return item.keys() + else: + return [] + + +def get_value(items: dict | BaseModel, key: str) -> object: + if isinstance(items, BaseModel): + return getattr(items, key) + elif isinstance(items, dict): + return items[key] + + +def check_circular_references( + data: dict[str, object], + current: object = None, + seen: list[str] = None, + path: list[str] = None, + name: str = None, +): + current = current if current is not None else data + seen = seen if seen is not None else [] + path = path if path is not None else [] + + if not isinstance(current, (dict, BaseModel)): + return + + logger.debug(f"current is: {current}") + logger.debug(f"seen is: {seen}") + logger.debug(f"path is: {path}") + + for key in item_keys(current): + value = get_value(current, key) + + # Pass through objects we can recurse or strings if they're keys in `data` + if isinstance(value, str): + if value not in data: + continue + elif is_leaf_node(value): + continue + + path_key = [*path, key] + + if isinstance(value, str): # implied value is also in data by above check + seen_key = [*seen, *([key, value] if len(seen) == 0 else [value])] + if value in seen: + raise CircularReferenceError(seen_key, path_key, name) + else: + new_current = {k: v for k, v in data.items() if k == value} + check_circular_references(data, new_current, seen_key, path_key, name) + else: + check_circular_references(data, value, seen, path_key, name) + + +class CircularReferenceError(Exception): + def __init__(self, seen: list[str], path: list[str], name: str = None): + self.seen = seen + self.path = path + self.name = name + + msg_name = "" if not name else f" in '{name}'" + + message = f'Circular reference detected{msg_name}.\nRefs : {" -> ".join(seen)}\nVia path: {" -> ".join(path)}' + super().__init__(message) diff --git a/pkg-py/tests/test_utils.py b/pkg-py/tests/test_utils.py index e4ce4d44..78d9d2ad 100644 --- a/pkg-py/tests/test_utils.py +++ b/pkg-py/tests/test_utils.py @@ -3,7 +3,8 @@ import logging from typing import Union -from brand_yaml._brand_utils import BrandLightDarkString, BrandWith +import pytest +from brand_yaml._brand_utils import BrandLightDarkString, BrandWith, CircularReferenceError logging.basicConfig(level=logging.DEBUG) @@ -100,3 +101,13 @@ class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): assert isinstance(thing.with_["both"], BrandLightDarkString) assert isinstance(thing.the, BrandLightDarkString) + +def test_brand_with_errors_on_circular_references(): + with pytest.raises(CircularReferenceError, match="a -> b -> a"): + BrandWith.model_validate({"with_": {"a": "b", "b": "a"}}) + + with pytest.raises(CircularReferenceError, match="a -> b -> c -> a"): + BrandWith.model_validate({"with_": {"a": "b", "b": "c", "c": "a"}}) + + with pytest.raises(CircularReferenceError, match="a -> d -> b -> a"): + BrandWith.model_validate({"with_": {"a": "d", "b": "a", "d": {"x": "e", "y": "b"}}}) \ No newline at end of file From 0c6d3c75622219eaa4c5beb51002d1d791246c29 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 12:43:09 -0400 Subject: [PATCH 028/119] chore: Simplify file names --- pkg-py/src/brand_yaml/__init__.py | 6 +- pkg-py/src/brand_yaml/_brand.py | 166 ++++++++++++++++++ .../brand_yaml/{_brand_utils.py => _utils.py} | 0 .../brand_yaml/{_brand_color.py => color.py} | 2 +- .../brand_yaml/{_brand_logo.py => logo.py} | 2 +- .../brand_yaml/{_brand_meta.py => meta.py} | 0 pkg-py/tests/test_meta.py | 2 +- pkg-py/tests/test_utils.py | 2 +- 8 files changed, 173 insertions(+), 7 deletions(-) create mode 100644 pkg-py/src/brand_yaml/_brand.py rename pkg-py/src/brand_yaml/{_brand_utils.py => _utils.py} (100%) rename pkg-py/src/brand_yaml/{_brand_color.py => color.py} (99%) rename pkg-py/src/brand_yaml/{_brand_logo.py => logo.py} (95%) rename pkg-py/src/brand_yaml/{_brand_meta.py => meta.py} (100%) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 77ef685d..b05aa009 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -6,9 +6,9 @@ from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML -from ._brand_logo import BrandLogo -from ._brand_meta import BrandMeta -from ._brand_color import BrandColor +from .logo import BrandLogo +from .meta import BrandMeta +from .color import BrandColor yaml = YAML() diff --git a/pkg-py/src/brand_yaml/_brand.py b/pkg-py/src/brand_yaml/_brand.py new file mode 100644 index 00000000..669efdb0 --- /dev/null +++ b/pkg-py/src/brand_yaml/_brand.py @@ -0,0 +1,166 @@ +from __future__ import annotations + +from typing import Any, List, Literal, Union + +from pydantic import BaseModel, ConfigDict, HttpUrl, RootModel + + +class Brand(BaseModel): + model_config = ConfigDict(extra="ignore") + + meta: BrandMeta = None + logo: str | BrandLogo = None + color: BrandColor = None + typography: BrandTypography = None + defaults: dict[str, Any] = None + +class BrandStringLightDark(BaseModel): + model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) + + light: str | dict = None + dark: str | dict = None + + +class BrandLogo(BaseModel): + model_config = ConfigDict(extra="forbid") + + with_: dict[str, str | BrandStringLightDark] = None + small: str | BrandStringLightDark = None + medium: str | BrandStringLightDark = None + large: str | BrandStringLightDark = None + + +type BrandColorValue = str + + +class BrandColor(BaseModel): + model_config = ConfigDict(extra="forbid") + + with_: dict[str, BrandColorValue] = None + foreground: BrandColorValue = None + background: BrandColorValue = None + primary: BrandColorValue = None + secondary: BrandColorValue = None + tertiary: BrandColorValue = None + success: BrandColorValue = None + info: BrandColorValue = None + warning: BrandColorValue = None + danger: BrandColorValue = None + light: BrandColorValue = None + dark: BrandColorValue = None + emphasis: BrandColorValue = None + link: BrandColorValue = None + + +type BrandNamedColorType = Literal[ + "foreground", + "background", + "primary", + "secondary", + "tertiary", + "success", + "info", + "warning", + "danger", + "light", + "dark", + "emphasis", + "link", +] +BrandNamedColorValues: BrandNamedColorType = ( + "foreground", + "background", + "primary", + "secondary", + "tertiary", + "success", + "info", + "warning", + "danger", + "light", + "dark", + "emphasis", + "link", +) + + +class BrandTypography(BaseModel): + with_: BrandFont | list[BrandFont] = None + base: BrandTypographyOptions = None + headings: BrandTypographyOptionsNoSize = None + monospace: BrandTypographyOptions = None + emphasis: BrandTypographyEmphasis = None + link: BrandTypographyLink = None + + +class BrandTypographyOptions(BaseModel): + family: str = None + size: str = None + line_height: str = None + weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None + style: BrandTypographyFontStyleType | list[BrandTypographyFontStyleType] = None + color: str | BrandNamedColorType = None + background_color: str | BrandNamedColorType = None + + +class BrandTypographyOptionsNoSize(BaseModel): + family: str = None + line_height: str = None + weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None + style: BrandTypographyFontStyleType | list[BrandTypographyFontStyleType] = None + color: str | BrandNamedColorType = None + background_color: str | BrandNamedColorType = None + + +class BrandTypographyEmphasis(BaseModel): + weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None + color: str | BrandNamedColorType = None + background_color: str | BrandNamedColorType = None + + +class BrandTypographyLink(BaseModel): + weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None + decoration: str = None + color: str | BrandNamedColorType = None + background_color: str | BrandNamedColorType = None + + +type BrandTypographyFontWeightType = Literal[ + 100, 200, 300, 400, 500, 600, 700, 800, 900 +] +BrandTypographyFontWeightValues: BrandTypographyFontWeightType = ( + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, +) + + +type BrandTypographyFontStyleType = Literal["normal", "italic"] +BrandTypographyFontStyleValues: BrandTypographyFontStyleType = ("normal", "italic") + + +class BrandTypographyFontGoogle(BaseModel): + family: str + weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = 400 + style: BrandTypographyFontStyleType | list[BrandTypographyFontStyleType] = "normal" + display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" + + +class BrandFontFile(BaseModel): + model_config = ConfigDict(extra="forbid") + family: str + files: str | list[str] = None + + +class BrandFontFamily(RootModel): + root: str + + +class BrandFont(RootModel): + root: List[Union[BrandTypographyFontGoogle, BrandFontFile, BrandFontFamily]] diff --git a/pkg-py/src/brand_yaml/_brand_utils.py b/pkg-py/src/brand_yaml/_utils.py similarity index 100% rename from pkg-py/src/brand_yaml/_brand_utils.py rename to pkg-py/src/brand_yaml/_utils.py diff --git a/pkg-py/src/brand_yaml/_brand_color.py b/pkg-py/src/brand_yaml/color.py similarity index 99% rename from pkg-py/src/brand_yaml/_brand_color.py rename to pkg-py/src/brand_yaml/color.py index f8eda0d0..67bf5aa5 100644 --- a/pkg-py/src/brand_yaml/_brand_color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -4,7 +4,7 @@ from pydantic import ConfigDict, Field -from ._brand_utils import BrandWith +from ._utils import BrandWith class BrandColor(BrandWith[str]): diff --git a/pkg-py/src/brand_yaml/_brand_logo.py b/pkg-py/src/brand_yaml/logo.py similarity index 95% rename from pkg-py/src/brand_yaml/_brand_logo.py rename to pkg-py/src/brand_yaml/logo.py index 1b44954d..5687b891 100644 --- a/pkg-py/src/brand_yaml/_brand_logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -3,7 +3,7 @@ from typing import Union from pydantic import ConfigDict -from ._brand_utils import BrandLightDark, BrandWith +from ._utils import BrandLightDark, BrandWith class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): diff --git a/pkg-py/src/brand_yaml/_brand_meta.py b/pkg-py/src/brand_yaml/meta.py similarity index 100% rename from pkg-py/src/brand_yaml/_brand_meta.py rename to pkg-py/src/brand_yaml/meta.py diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index cf3cee4c..f7aeec91 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -2,7 +2,7 @@ import pytest from brand_yaml import read_brand_yaml -from brand_yaml._brand_meta import BrandMeta +from brand_yaml.meta import BrandMeta from pydantic import HttpUrl from utils import path_examples diff --git a/pkg-py/tests/test_utils.py b/pkg-py/tests/test_utils.py index 78d9d2ad..27fa65fd 100644 --- a/pkg-py/tests/test_utils.py +++ b/pkg-py/tests/test_utils.py @@ -4,7 +4,7 @@ from typing import Union import pytest -from brand_yaml._brand_utils import BrandLightDarkString, BrandWith, CircularReferenceError +from brand_yaml._utils import BrandLightDarkString, BrandWith, CircularReferenceError logging.basicConfig(level=logging.DEBUG) From 6439e210683ac2efadd7b3e5570617c76cf25c96 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 12:45:23 -0400 Subject: [PATCH 029/119] chore: rename brand_yaml._utils --> brand_yaml._defs --- pkg-py/src/brand_yaml/{_utils.py => _defs.py} | 0 pkg-py/src/brand_yaml/color.py | 2 +- pkg-py/src/brand_yaml/logo.py | 2 +- pkg-py/tests/{test_utils.py => test_defs.py} | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename pkg-py/src/brand_yaml/{_utils.py => _defs.py} (100%) rename pkg-py/tests/{test_utils.py => test_defs.py} (97%) diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_defs.py similarity index 100% rename from pkg-py/src/brand_yaml/_utils.py rename to pkg-py/src/brand_yaml/_defs.py diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 67bf5aa5..335cb225 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -4,7 +4,7 @@ from pydantic import ConfigDict, Field -from ._utils import BrandWith +from ._defs import BrandWith class BrandColor(BrandWith[str]): diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 5687b891..bc631225 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -3,7 +3,7 @@ from typing import Union from pydantic import ConfigDict -from ._utils import BrandLightDark, BrandWith +from ._defs import BrandLightDark, BrandWith class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): diff --git a/pkg-py/tests/test_utils.py b/pkg-py/tests/test_defs.py similarity index 97% rename from pkg-py/tests/test_utils.py rename to pkg-py/tests/test_defs.py index 27fa65fd..853ff531 100644 --- a/pkg-py/tests/test_utils.py +++ b/pkg-py/tests/test_defs.py @@ -4,7 +4,7 @@ from typing import Union import pytest -from brand_yaml._utils import BrandLightDarkString, BrandWith, CircularReferenceError +from brand_yaml._defs import BrandLightDarkString, BrandWith, CircularReferenceError logging.basicConfig(level=logging.DEBUG) From 2a7492794bb4425aaf7afecb2517a30e3f29f830 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 14:46:11 -0400 Subject: [PATCH 030/119] chore: Generalize definition resolution beyond just `self.with_` This will make it possible to blend color.{named} and color.with, plus other more complicated scenarios, without having to rewrite the recursive definition resolution for those scenarios individually. --- pkg-py/src/brand_yaml/_defs.py | 174 +++++++++++++++++++++------------ 1 file changed, 111 insertions(+), 63 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 070602c6..d47763c1 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -30,6 +30,10 @@ def is_leaf_node(value: Any) -> bool: return not isinstance(value, (dict, BaseModel)) +def is_non_str_leaf_node(value: Any) -> bool: + return not isinstance(value, (dict, BaseModel, str)) + + class BrandWith(BaseModel, Generic[T]): model_config = ConfigDict( extra="ignore", @@ -47,6 +51,7 @@ def validate_with_(cls, value: dict[str, T] | None) -> dict[str, T] | None: if value is None: return value + logger.debug("validating field with_ by checking for circular references") check_circular_references(value, name="with") return value @@ -55,79 +60,122 @@ def resolve_with_values(self, __context: Any) -> None: if self.with_ is None: return self - logger.debug("resolving with_ values") - self._replace_with_recursively() + logger.debug("validating model and resolving with_ values") + defs_replace_recursively(self.with_, self, name="with_") return self - def __setattr__(self, name: str, value: Any) -> None: - super().__setattr__(name, value) - if name != "with_": - self._replace_with_recursively(value) - - def _get_with(self, key: str, level=0) -> object: - """ - Finds `key` in `with_`, which may require recursively resolving nested - values from `with_`. - """ - if key not in self.with_: - return key - - # Note that this is simplified by the fact that we've already confirmed - # that no circular references exist in `with_`. - - with_value = deepcopy(self.with_[key]) - logger.debug( - level_indent(f"key {key} is in with_ with value {with_value!r}", level) - ) - - if is_leaf_node(self.with_[key]): - return with_value - else: - self._replace_with_recursively(with_value, level) - return with_value - def _replace_with_recursively(self, items: dict | BaseModel | None = None, level=0): - if level > 50: - logger.error("BrandWith recursion limit reached") - return +def defs_get(defs: dict[str, object], key: str, level: int = 0) -> object: + """ + Finds `key` in `with_`, which may require recursively resolving nested + values from `with_`. - if items is None: - items = self + Parameters + ---------- - if not isinstance(items, (dict, BaseModel)): - return + defs + A dictionary of definitions. - for key in item_keys(items): - value = get_value(items, key) + key + The key to look up in `defs`. - if value is self.with_: - # We replace `with_` when resolving sibling fields - continue + Returns + ------- + : + The value of `key` in `defs`, with any internal references to top-level + keys in `defs` also resolved. If `defs[key]` returns a dictionary or + pydantic model, internal references to definitions are also replaced. + """ + if key not in defs: + return key + + # Note that we assume that `defs` has already been checked for circular + # references, so we don't need to check for them here. - logger.debug(level_indent(f"inspecting key {key}", level)) - if isinstance(value, str) and value in self.with_: - new_value = self._get_with(value, level + 1) - logger.debug( - level_indent( - f"replacing key {key} with value from {value}: {new_value!r}", - level, - ) + with_value = deepcopy(defs[key]) + logger.debug( + level_indent(f"key {key} is in with_ with value {with_value!r}", level) + ) + + if is_leaf_node(defs[key]): + return with_value + else: + defs_replace_recursively(defs, with_value, level=level) + return with_value + + +def defs_replace_recursively( + defs: dict[str, object], + items: dict | BaseModel = None, + level: int = 0, + name: str = None, +): + """ + Recursively replace string values in `items` with their definition in + `defs`. An item in `items` is replaced if it is a string and exactly matches + a key in the `defs` dictionary. Definitions in `defs` can refer to other + definitions, provided that no definitions are circular, e.g. `a -> b -> a`. + + Parameters + ---------- + defs + A dictionary of definitions. + + items + A dictionary or pydantic model to replace values in. + + level + The current recursion level. Used internally and for logging. + + Returns + ------- + : + Nothing. `items` is modified in place. Note that when values in items + refer to definitions in `defs`, the are replaced with copies of the + definition. + """ + if level == 0: + logger.debug("Checking for circular references") + check_circular_references(defs, name=name) + + if level > 50: + logger.error("BrandWith recursion limit reached") + return + + if not isinstance(items, (dict, BaseModel)): + return + + for key in item_keys(items): + value = get_value(items, key) + + if value is defs: + # We replace internal def references when resolving sibling fields + continue + + logger.debug(level_indent(f"inspecting key {key}", level)) + if isinstance(value, str) and value in defs: + new_value = defs_get(defs, value, level=level + 1) + logger.debug( + level_indent( + f"replacing key {key} with definition from {value}: {new_value!r}", + level, ) - if isinstance(items, BaseModel): - setattr(items, key, new_value) - elif isinstance(items, dict): - items[key] = new_value - elif isinstance(value, (dict, BaseModel)): - # TODO: we may want to avoid recursing into child BrandWith instances - logger.debug(level_indent(f"recursing into {key}", level)) - self._replace_with_recursively(value, level + 1) - else: - logger.debug( - level_indent( - f"skipping {key}, not replaceable (or not a dict or pydantic model)", - level, - ) + ) + if isinstance(items, BaseModel): + setattr(items, key, new_value) + elif isinstance(items, dict): + items[key] = new_value + elif isinstance(value, (dict, BaseModel)): + # TODO: we may want to avoid recursing into child BrandWith instances + logger.debug(level_indent(f"recursing into {key}", level)) + defs_replace_recursively(defs, value, level=level + 1) + else: + logger.debug( + level_indent( + f"skipping {key}, not replaceable (or not a dict or pydantic model)", + level, ) + ) def level_indent(x: str, level: int) -> str: From b7e534abde5cd7d72ca6f026d075b102a552cea7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 14:47:46 -0400 Subject: [PATCH 031/119] chore: log format and logging in tests --- pkg-py/src/brand_yaml/_utils_logging.py | 2 +- pkg-py/tests/test_defs.py | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/brand_yaml/_utils_logging.py b/pkg-py/src/brand_yaml/_utils_logging.py index 8172185a..beba25e8 100644 --- a/pkg-py/src/brand_yaml/_utils_logging.py +++ b/pkg-py/src/brand_yaml/_utils_logging.py @@ -9,7 +9,7 @@ def log_add_console_stream_handler(): if isinstance(handler, logging.StreamHandler): return - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + formatter = logging.Formatter('%(asctime)s %(levelname)s - %(name)s - %(funcName)s - %(message)s') ch = logging.StreamHandler() ch.setFormatter(formatter) logger.addHandler(ch) diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index 853ff531..6d79ad15 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -1,12 +1,12 @@ from __future__ import annotations -import logging from typing import Union import pytest from brand_yaml._defs import BrandLightDarkString, BrandWith, CircularReferenceError -logging.basicConfig(level=logging.DEBUG) +# from brand_yaml._utils_logging import log_set_debug +# log_set_debug() def test_brand_with_simple(): @@ -68,7 +68,7 @@ class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): thing = BrandThing( with_={ "sm": "small-light", - "md": {"light": "medium-light", "dark": "medium-dark"} + "md": {"light": "medium-light", "dark": "medium-dark"}, }, small={"light": "sm", "dark": "small-dark"}, medium="md", @@ -82,6 +82,7 @@ class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): assert isinstance(thing.small, BrandLightDarkString) assert isinstance(thing.medium, BrandLightDarkString) + def test_brand_with_nested(): class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): the: Union[str, BrandLightDarkString] = None @@ -90,7 +91,7 @@ class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): with_={ "light": "the-light", "dark": "the-dark", - "both": {"light": "the-light", "dark": "the-dark"} + "both": {"light": "the-light", "dark": "the-dark"}, }, the="both", ) @@ -102,6 +103,7 @@ class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): assert isinstance(thing.with_["both"], BrandLightDarkString) assert isinstance(thing.the, BrandLightDarkString) + def test_brand_with_errors_on_circular_references(): with pytest.raises(CircularReferenceError, match="a -> b -> a"): BrandWith.model_validate({"with_": {"a": "b", "b": "a"}}) @@ -110,4 +112,6 @@ def test_brand_with_errors_on_circular_references(): BrandWith.model_validate({"with_": {"a": "b", "b": "c", "c": "a"}}) with pytest.raises(CircularReferenceError, match="a -> d -> b -> a"): - BrandWith.model_validate({"with_": {"a": "d", "b": "a", "d": {"x": "e", "y": "b"}}}) \ No newline at end of file + BrandWith.model_validate( + {"with_": {"a": "d", "b": "a", "d": {"x": "e", "y": "b"}}} + ) From 4083205a40434f330565dce2ee6d541e7b539b4e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 16:34:37 -0400 Subject: [PATCH 032/119] chore: configure ruff for package and vscode --- .vscode/extensions.json | 5 +++ .vscode/settings.json | 10 +++++ pkg-py/pyproject.toml | 54 +++++++++++++++++++++++++ pkg-py/src/brand_yaml/__init__.py | 2 +- pkg-py/src/brand_yaml/_defs.py | 20 +++++++-- pkg-py/src/brand_yaml/_utils_logging.py | 8 +++- pkg-py/src/brand_yaml/color.py | 1 + pkg-py/src/brand_yaml/logo.py | 1 + pkg-py/tests/test_color.py | 7 +++- pkg-py/tests/test_defs.py | 7 +++- pkg-py/tests/test_logo.py | 17 +++++--- pkg-py/tests/test_meta.py | 9 +++-- pkg-py/tests/utils/__init__.py | 3 +- 13 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..424b18fc --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "charliermarsh.ruff" + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..e64211ad --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "[python]": { + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff" + } +} \ No newline at end of file diff --git a/pkg-py/pyproject.toml b/pkg-py/pyproject.toml index f594b84f..041b1daf 100644 --- a/pkg-py/pyproject.toml +++ b/pkg-py/pyproject.toml @@ -24,3 +24,57 @@ dev = [ [build-system] requires = ["hatchling"] build-backend = "hatchling.build" + +[tool.ruff] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".ipynb_checkpoints", + ".mypy_cache", + ".nox", + ".pants.d", + ".pyenv", + ".pytest_cache", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + ".vscode", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "site-packages", + "venv", +] + +line-length = 80 +indent-width = 4 + +target-version = "py39" + +[tool.ruff.lint] +select = ['E', 'F', 'W', 'A', 'PLC', 'PLE', 'PLW', 'I'] +ignore = ["E501"] + +# Allow fix for all enabled rules (when `--fix`) is provided. +fixable = ["ALL"] +unfixable = [] + +# Allow unused variables when underscore-prefixed. +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" +docstring-code-format = true +docstring-code-line-length = "dynamic" \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index b05aa009..3fdd4699 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -6,9 +6,9 @@ from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML +from .color import BrandColor from .logo import BrandLogo from .meta import BrandMeta -from .color import BrandColor yaml = YAML() diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index d47763c1..5305747d 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -4,7 +4,13 @@ from textwrap import indent from typing import Any, Generic, Optional, TypeVar, Union -from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_validator, + model_validator, +) from ._utils_logging import logger @@ -51,7 +57,9 @@ def validate_with_(cls, value: dict[str, T] | None) -> dict[str, T] | None: if value is None: return value - logger.debug("validating field with_ by checking for circular references") + logger.debug( + "validating field with_ by checking for circular references" + ) check_circular_references(value, name="with") return value @@ -228,13 +236,17 @@ def check_circular_references( path_key = [*path, key] - if isinstance(value, str): # implied value is also in data by above check + if isinstance( + value, str + ): # implied value is also in data by above check seen_key = [*seen, *([key, value] if len(seen) == 0 else [value])] if value in seen: raise CircularReferenceError(seen_key, path_key, name) else: new_current = {k: v for k, v in data.items() if k == value} - check_circular_references(data, new_current, seen_key, path_key, name) + check_circular_references( + data, new_current, seen_key, path_key, name + ) else: check_circular_references(data, value, seen, path_key, name) diff --git a/pkg-py/src/brand_yaml/_utils_logging.py b/pkg-py/src/brand_yaml/_utils_logging.py index beba25e8..2e109cce 100644 --- a/pkg-py/src/brand_yaml/_utils_logging.py +++ b/pkg-py/src/brand_yaml/_utils_logging.py @@ -4,16 +4,20 @@ if len(logger.handlers) == 0: logger.addHandler(logging.NullHandler()) + def log_add_console_stream_handler(): for handler in logger.handlers: if isinstance(handler, logging.StreamHandler): return - - formatter = logging.Formatter('%(asctime)s %(levelname)s - %(name)s - %(funcName)s - %(message)s') + + formatter = logging.Formatter( + "%(asctime)s %(levelname)s - %(name)s - %(funcName)s - %(message)s" + ) ch = logging.StreamHandler() ch.setFormatter(formatter) logger.addHandler(ch) + def log_set_debug(): log_add_console_stream_handler() logger.setLevel(logging.DEBUG) diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 335cb225..a87c4e8a 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -62,6 +62,7 @@ class BrandColor(BrandWith[str]): used. """ + model_config = ConfigDict(extra="forbid") foreground: Optional[str] = Field( diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index bc631225..842bbc87 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -3,6 +3,7 @@ from typing import Union from pydantic import ConfigDict + from ._defs import BrandLightDark, BrandWith diff --git a/pkg-py/tests/test_color.py b/pkg-py/tests/test_color.py index c8e4f7c2..f815937c 100644 --- a/pkg-py/tests/test_color.py +++ b/pkg-py/tests/test_color.py @@ -1,8 +1,10 @@ from __future__ import annotations -from brand_yaml import read_brand_yaml from utils import path_examples +from brand_yaml import read_brand_yaml + + def test_brand_color_posit_direct(): brand = read_brand_yaml(path_examples("brand-color-posit-direct.yml")) @@ -18,6 +20,7 @@ def test_brand_color_posit_direct(): assert brand.color.light == "#FFFFFF" assert brand.color.dark == "#404041" + def test_brand_color_posit_with(): brand = read_brand_yaml(path_examples("brand-color-posit-with.yml")) @@ -41,5 +44,5 @@ def test_brand_color_posit_with(): "orange": "#EE6331", "green": "#72994E", "teal": "#419599", - "burgundy": "#9A4665" + "burgundy": "#9A4665", } diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index 6d79ad15..34ae71af 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -3,7 +3,12 @@ from typing import Union import pytest -from brand_yaml._defs import BrandLightDarkString, BrandWith, CircularReferenceError + +from brand_yaml._defs import ( + BrandLightDarkString, + BrandWith, + CircularReferenceError, +) # from brand_yaml._utils_logging import log_set_debug # log_set_debug() diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 6eaaa38a..be1f7178 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -1,36 +1,41 @@ from __future__ import annotations -from brand_yaml import read_brand_yaml from utils import path_examples +from brand_yaml import read_brand_yaml + + def test_brand_logo_single(): brand = read_brand_yaml(path_examples("brand-logo-single.yml")) - + assert brand.logo == "posit.png" + def test_brand_logo_simple(): brand = read_brand_yaml(path_examples("brand-logo-simple.yml")) - + assert brand.logo.small == "icon.png" assert brand.logo.medium == "logo.png" assert brand.logo.large == "display.svg" + def test_brand_logo_light_dark(): brand = read_brand_yaml(path_examples("brand-logo-light-dark.yml")) - + assert brand.logo.small == "icon.png" assert brand.logo.medium.light == "logo-light.png" assert brand.logo.medium.dark == "logo-dark.png" assert brand.logo.large == "display.svg" + def test_brand_logo_full(): brand_logo_full = read_brand_yaml(path_examples("brand-logo-full.yml")) assert brand_logo_full.logo.small == "favicon.png" - + assert brand_logo_full.logo.medium.light == "full-color.png" assert brand_logo_full.logo.medium.dark == "full-color-reverse.png" - + assert brand_logo_full.logo.large == "full-color.svg" # replace small with new value from "with" diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index f7aeec91..606d4ef6 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -1,11 +1,12 @@ from __future__ import annotations import pytest -from brand_yaml import read_brand_yaml -from brand_yaml.meta import BrandMeta from pydantic import HttpUrl from utils import path_examples +from brand_yaml import read_brand_yaml +from brand_yaml.meta import BrandMeta + def test_brand_meta(): meta = BrandMeta( @@ -53,7 +54,9 @@ def test_brand_meta_yaml_full(): "https://linkedin.com/company/very-big-corp" ) assert brand.meta.link.twitter == HttpUrl("https://twitter.com/VeryBigCorp") - assert brand.meta.link.facebook == HttpUrl("https://facebook.com/Very-Big-Corp") + assert brand.meta.link.facebook == HttpUrl( + "https://facebook.com/Very-Big-Corp" + ) def test_brand_meta_yaml_small(): diff --git a/pkg-py/tests/utils/__init__.py b/pkg-py/tests/utils/__init__.py index ebc21261..7ccffcb8 100644 --- a/pkg-py/tests/utils/__init__.py +++ b/pkg-py/tests/utils/__init__.py @@ -1,5 +1,6 @@ from pathlib import Path + def path_examples(*args) -> Path: - repo_root = Path(__file__).parent.parent.parent.parent + repo_root = Path(__file__).parent.parent.parent.parent return repo_root / "examples" / Path(*args) From 587ba4175d8802aff3e30edfd3483dd9d68e3a0d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 16:35:13 -0400 Subject: [PATCH 033/119] chore: remove accidentally commited scratch file --- pkg-py/src/brand_yaml/_brand.py | 166 -------------------------------- 1 file changed, 166 deletions(-) delete mode 100644 pkg-py/src/brand_yaml/_brand.py diff --git a/pkg-py/src/brand_yaml/_brand.py b/pkg-py/src/brand_yaml/_brand.py deleted file mode 100644 index 669efdb0..00000000 --- a/pkg-py/src/brand_yaml/_brand.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import annotations - -from typing import Any, List, Literal, Union - -from pydantic import BaseModel, ConfigDict, HttpUrl, RootModel - - -class Brand(BaseModel): - model_config = ConfigDict(extra="ignore") - - meta: BrandMeta = None - logo: str | BrandLogo = None - color: BrandColor = None - typography: BrandTypography = None - defaults: dict[str, Any] = None - -class BrandStringLightDark(BaseModel): - model_config = ConfigDict(extra="ignore", str_strip_whitespace=True) - - light: str | dict = None - dark: str | dict = None - - -class BrandLogo(BaseModel): - model_config = ConfigDict(extra="forbid") - - with_: dict[str, str | BrandStringLightDark] = None - small: str | BrandStringLightDark = None - medium: str | BrandStringLightDark = None - large: str | BrandStringLightDark = None - - -type BrandColorValue = str - - -class BrandColor(BaseModel): - model_config = ConfigDict(extra="forbid") - - with_: dict[str, BrandColorValue] = None - foreground: BrandColorValue = None - background: BrandColorValue = None - primary: BrandColorValue = None - secondary: BrandColorValue = None - tertiary: BrandColorValue = None - success: BrandColorValue = None - info: BrandColorValue = None - warning: BrandColorValue = None - danger: BrandColorValue = None - light: BrandColorValue = None - dark: BrandColorValue = None - emphasis: BrandColorValue = None - link: BrandColorValue = None - - -type BrandNamedColorType = Literal[ - "foreground", - "background", - "primary", - "secondary", - "tertiary", - "success", - "info", - "warning", - "danger", - "light", - "dark", - "emphasis", - "link", -] -BrandNamedColorValues: BrandNamedColorType = ( - "foreground", - "background", - "primary", - "secondary", - "tertiary", - "success", - "info", - "warning", - "danger", - "light", - "dark", - "emphasis", - "link", -) - - -class BrandTypography(BaseModel): - with_: BrandFont | list[BrandFont] = None - base: BrandTypographyOptions = None - headings: BrandTypographyOptionsNoSize = None - monospace: BrandTypographyOptions = None - emphasis: BrandTypographyEmphasis = None - link: BrandTypographyLink = None - - -class BrandTypographyOptions(BaseModel): - family: str = None - size: str = None - line_height: str = None - weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None - style: BrandTypographyFontStyleType | list[BrandTypographyFontStyleType] = None - color: str | BrandNamedColorType = None - background_color: str | BrandNamedColorType = None - - -class BrandTypographyOptionsNoSize(BaseModel): - family: str = None - line_height: str = None - weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None - style: BrandTypographyFontStyleType | list[BrandTypographyFontStyleType] = None - color: str | BrandNamedColorType = None - background_color: str | BrandNamedColorType = None - - -class BrandTypographyEmphasis(BaseModel): - weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None - color: str | BrandNamedColorType = None - background_color: str | BrandNamedColorType = None - - -class BrandTypographyLink(BaseModel): - weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = None - decoration: str = None - color: str | BrandNamedColorType = None - background_color: str | BrandNamedColorType = None - - -type BrandTypographyFontWeightType = Literal[ - 100, 200, 300, 400, 500, 600, 700, 800, 900 -] -BrandTypographyFontWeightValues: BrandTypographyFontWeightType = ( - 100, - 200, - 300, - 400, - 500, - 600, - 700, - 800, - 900, -) - - -type BrandTypographyFontStyleType = Literal["normal", "italic"] -BrandTypographyFontStyleValues: BrandTypographyFontStyleType = ("normal", "italic") - - -class BrandTypographyFontGoogle(BaseModel): - family: str - weight: BrandTypographyFontWeightType | list[BrandTypographyFontWeightType] = 400 - style: BrandTypographyFontStyleType | list[BrandTypographyFontStyleType] = "normal" - display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" - - -class BrandFontFile(BaseModel): - model_config = ConfigDict(extra="forbid") - family: str - files: str | list[str] = None - - -class BrandFontFamily(RootModel): - root: str - - -class BrandFont(RootModel): - root: List[Union[BrandTypographyFontGoogle, BrandFontFile, BrandFontFamily]] From b48a55dcf8645d91200853e1e84647504b694b08 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 5 Sep 2024 17:10:34 -0400 Subject: [PATCH 034/119] feat(color): Resolve named colors as with `with_` field --- examples/brand-color-posit-internal.yml | 21 ++++++++++++ pkg-py/src/brand_yaml/_defs.py | 4 +-- pkg-py/src/brand_yaml/color.py | 43 +++++++++++++++++++++++-- pkg-py/tests/test_color.py | 20 ++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 examples/brand-color-posit-internal.yml diff --git a/examples/brand-color-posit-internal.yml b/examples/brand-color-posit-internal.yml new file mode 100644 index 00000000..8bafd141 --- /dev/null +++ b/examples/brand-color-posit-internal.yml @@ -0,0 +1,21 @@ +color: + with: + white: "#FFFFFF" + black: "#151515" + blue: "#447099" + orange: "#EE6331" + green: "#72994E" + teal: "#419599" + burgundy: "#9A4665" + + foreground: black + background: white + primary: blue + secondary: "#707073" + tertiary: "#C2C2C4" + success: green + info: primary + warning: orange + danger: burgundy + light: background + dark: "#404041" \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 5305747d..b1faf573 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -64,7 +64,7 @@ def validate_with_(cls, value: dict[str, T] | None) -> dict[str, T] | None: return value @model_validator(mode="after") - def resolve_with_values(self, __context: Any) -> None: + def resolve_with_values(self): if self.with_ is None: return self @@ -156,7 +156,7 @@ def defs_replace_recursively( for key in item_keys(items): value = get_value(items, key) - if value is defs: + if value is defs or value == "with_": # We replace internal def references when resolving sibling fields continue diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index a87c4e8a..7c11f4ed 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -1,10 +1,11 @@ from __future__ import annotations +from copy import deepcopy from typing import Optional -from pydantic import ConfigDict, Field +from pydantic import ConfigDict, Field, model_validator -from ._defs import BrandWith +from ._defs import BrandWith, defs_replace_recursively class BrandColor(BrandWith[str]): @@ -63,7 +64,43 @@ class BrandColor(BrandWith[str]): """ - model_config = ConfigDict(extra="forbid") + model_config = ConfigDict( + extra="forbid", + revalidate_instances="always", + validate_assignment=True, + ) + + _color_fields = [ + "foreground", + "background", + "primary", + "secondary", + "tertiary", + "success", + "info", + "warning", + "danger", + "light", + "dark", + "emphasis", + "link", + ] + + @model_validator(mode="after") + def resolve_with_values(self): + if self.with_ is not None: + defs_replace_recursively(self.with_, self, name="with_") + + full_defs = deepcopy(self.with_) if self.with_ is not None else {} + full_defs.update( + { + k: v + for k, v in self.model_dump().items() + if k in self._color_fields and v is not None + } + ) + defs_replace_recursively(full_defs, self, name="color") + return self foreground: Optional[str] = Field( default=None, diff --git a/pkg-py/tests/test_color.py b/pkg-py/tests/test_color.py index f815937c..a2d4b049 100644 --- a/pkg-py/tests/test_color.py +++ b/pkg-py/tests/test_color.py @@ -46,3 +46,23 @@ def test_brand_color_posit_with(): "teal": "#419599", "burgundy": "#9A4665", } + + +def test_brand_color_posit_internal(): + brand = read_brand_yaml(path_examples("brand-color-posit-internal.yml")) + + # Named theme colors are reused in BrandColor + assert brand.color.background == "#FFFFFF" + assert brand.color.primary == "#447099" + assert brand.color.info == brand.color.primary + assert brand.color.light == brand.color.background + + assert brand.color.with_ == { + "white": "#FFFFFF", + "black": "#151515", + "blue": "#447099", + "orange": "#EE6331", + "green": "#72994E", + "teal": "#419599", + "burgundy": "#9A4665", + } From 350641e04b47aacceacd04cebe61091ec3cc0dd1 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 6 Sep 2024 08:45:23 -0400 Subject: [PATCH 035/119] refactor(BrandColor): get color fields from model_fields --- pkg-py/src/brand_yaml/color.py | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 7c11f4ed..5dbb9301 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -70,33 +70,19 @@ class BrandColor(BrandWith[str]): validate_assignment=True, ) - _color_fields = [ - "foreground", - "background", - "primary", - "secondary", - "tertiary", - "success", - "info", - "warning", - "danger", - "light", - "dark", - "emphasis", - "link", - ] - @model_validator(mode="after") def resolve_with_values(self): if self.with_ is not None: defs_replace_recursively(self.with_, self, name="with_") + _color_fields = [k for k in self.model_fields.keys() if k != "with_"] + full_defs = deepcopy(self.with_) if self.with_ is not None else {} full_defs.update( { k: v for k, v in self.model_dump().items() - if k in self._color_fields and v is not None + if k in _color_fields and v is not None } ) defs_replace_recursively(full_defs, self, name="color") From a12dd987986a778b4fcf87807d976baef5a64a9b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 6 Sep 2024 16:49:41 -0400 Subject: [PATCH 036/119] feat(BrandTypography): Add `brand.typography` --- examples/brand-posit.yml | 43 ++--- pkg-py/src/brand_yaml/__init__.py | 7 +- pkg-py/src/brand_yaml/typography.py | 252 ++++++++++++++++++++++++++++ pkg-py/tests/test_typography.py | 50 ++++++ 4 files changed, 328 insertions(+), 24 deletions(-) create mode 100644 pkg-py/src/brand_yaml/typography.py create mode 100644 pkg-py/tests/test_typography.py diff --git a/examples/brand-posit.yml b/examples/brand-posit.yml index 7e2cfdd2..382c8bcc 100644 --- a/examples/brand-posit.yml +++ b/examples/brand-posit.yml @@ -15,7 +15,7 @@ logo: large: posit.svg color: - palette: + with: blue: "#447099" orange: "#EE6331" gray: "#404041" @@ -23,28 +23,29 @@ color: teal: "#419599" green: "#72994E" burgundy: "#9A4665" - theme: - foreground: "#151515" - background: "#FFFFFF" - primary: "#447099" - secondary: "#707073" - tertiary: "#C2C2C4" - success: "#72994E" - info: "#419599" - warning: "#EE6331" - danger: "#9A4665" - light: "#FFFFFF" - dark: "#404041" + foreground: "#151515" + background: "#FFFFFF" + primary: "#447099" + secondary: "#707073" + tertiary: "#C2C2C4" + success: "#72994E" + info: "#419599" + warning: "#EE6331" + danger: "#9A4665" + light: "#FFFFFF" + dark: "#404041" typography: - font: - - google: "Open Sans" - - google: "Fira Code" - - google: - family: "Roboto Slab" - weight: 600 - style: normal - display: block + fonts: + - source: google + family: Open Sans + - source: google + family: Fira Code + - source: google + family: Roboto Slab + weight: 600 + style: normal + display: block base: family: "Open Sans" diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 3fdd4699..f86d312c 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Union +from typing import Any, Union from pydantic import BaseModel, ConfigDict from ruamel.yaml import YAML @@ -9,6 +9,7 @@ from .color import BrandColor from .logo import BrandLogo from .meta import BrandMeta +from .typography import BrandTypography yaml = YAML() @@ -23,8 +24,8 @@ class Brand(BaseModel): meta: BrandMeta = None logo: Union[str, BrandLogo] = None color: BrandColor = None - # typography: BrandTypography = None - # defaults: dict[str, Any] = None + typography: BrandTypography = None + defaults: dict[str, Any] = None def read_brand_yaml(path: str | Path) -> Brand: diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py new file mode 100644 index 00000000..98ca46f1 --- /dev/null +++ b/pkg-py/src/brand_yaml/typography.py @@ -0,0 +1,252 @@ +from __future__ import annotations + +from pathlib import Path +from typing import Annotated, Literal, TypeVar, Union + +from pydantic import ( + BaseModel, + ConfigDict, + Discriminator, + Field, + RootModel, + Tag, + field_validator, + model_validator, +) + +T = TypeVar("T") + +SingleOrList = Union[T, list[T]] + + +BrandTypographyFontStyleType = Literal["normal", "italic"] +BrandTypographyFontWeightNamedType = Literal[ + "thin", + "extra-light", + "ultra-light", + "light", + "normal", + "regular", + "medium", + "semi-bold", + "demi-bold", + "bold", + "extra-bold", + "ultra-bold", + "black", +] +BrandTypographyFontWeightAllType = Union[ + float, int, BrandTypographyFontWeightNamedType +] + +BrandTypographyFontWeightSimpleType = Union[ + float, int, Literal["normal", "bold"] +] + +# https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping +BrandTypographyFontWeightMap = { + "thin": 100, + "extra-light": 200, + "ultra-light": 200, + "light": 300, + "normal": 400, + "regular": 400, + "medium": 500, + "semi-bold": 600, + "demi-bold": 600, + "bold": 700, + "extra-bold": 800, + "ultra-bold": 800, + "black": 900, +} + +# https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#font_formats +FontFormats = { + ".otc": "collection", + ".ttc": "collection", + ".eot": "embedded-opentype", + ".otf": "opentype", + ".ttf": "truetype", + ".svg": "svg", + ".svgz": "svg", + ".woff": "woff", + ".woff2": "woff2", +} + + +class BrandInvalidFontWeight(ValueError): + def __init__(self, value: any): + super().__init__( + f"Invalid font weight {value!r}. Expected a number divisible " + + "by 100 and between 100 and 900, or one of " + + f"{', '.join(BrandTypographyFontWeightMap.keys())}." + ) + + +class BrandUnsupportedFontFileFormat(ValueError): + def __init__(self, value: any): + supported = ("opentype", "truetype", "woff", "woff2") + super().__init__( + f"Unsupported font file {value!r}. Expected one of {', '.join(supported)}." + ) + + +def validate_font_weight( + value: int | str, +) -> BrandTypographyFontWeightSimpleType: + if isinstance(value, str): + if value in ("normal", "bold"): + return value + if value in BrandTypographyFontWeightMap: + return BrandTypographyFontWeightMap[value] + + try: + value = int(value) + except ValueError: + raise BrandInvalidFontWeight(value) + + if value < 100 or value > 900 or value % 100 != 0: + raise BrandInvalidFontWeight(value) + + return value + + +class BrandTypographyFontFile(BaseModel): + model_config = ConfigDict(extra="forbid") + + source: str + family: str + # These are the formats supported by brand_yaml, not *all* available formats + format: Literal["opentype", "truetype", "woff", "woff2"] = None + weight: BrandTypographyFontWeightSimpleType = "normal" + style: BrandTypographyFontStyleType = "normal" + + @field_validator("weight", mode="before") + @classmethod + def validate_weight(cls, value: int | str): + return validate_font_weight(value) + + @model_validator(mode="before") + @classmethod + def validate_format(cls, data: any): + source = data["source"] + if Path(source).suffix not in FontFormats: + raise BrandUnsupportedFontFileFormat(source) + + # Get file extension of `source` using built-in modules + data["format"] = FontFormats[Path(data["source"]).suffix] + + return data + + +class BrandTypographyFontGoogle(BaseModel): + model_config = ConfigDict(extra="forbid") + + source: Literal["google"] = "google" + family: str + weight: SingleOrList[BrandTypographyFontWeightAllType] = [400, 700] + style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] + display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" + + @field_validator("weight", mode="before") + @classmethod + def validate_weight(cls, value: SingleOrList[Union[int, str]]): + if isinstance(value, list): + return [validate_font_weight(x) for x in value] + else: + return validate_font_weight(value) + + +def brand_typography_font_discriminator( + x: dict[str, object] | BrandTypographyFontFile | BrandTypographyFontGoogle, +) -> Literal["google", "file"]: + if isinstance(x, BrandTypographyFontGoogle): + return "google" + elif isinstance(x, BrandTypographyFontFile): + return "file" + + value = x.get("source") + + if not isinstance(value, str): + pass + elif value == "google": + return "google" + elif Path(value).suffix: + return "file" + + raise ValueError( + "Unsupported font source {value!r}, must be a file path or {'google'!r}." + ) + + +class BrandNamedColor(RootModel): + root: str + + +class BrandTypographyOptionsBase(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + weight: BrandTypographyFontWeightSimpleType = None + color: BrandNamedColor = None + background_color: BrandNamedColor = Field(None, alias="background-color") + + @field_validator("weight", mode="before") + @classmethod + def validate_weight(cls, value: int | str): + return validate_font_weight(value) + + def __repr_args__(self): + fields = [f for f in self.model_fields.keys()] + values = [getattr(self, f) for f in fields] + return ((f, v) for f, v in zip(fields, values) if v is not None) + + +class BrandTypographyOptionsGenericText(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + family: str = None + line_height: float = Field(None, alias="line-height") + style: SingleOrList[BrandTypographyFontStyleType] = None + + +class BrandTypographyOptions( + BrandTypographyOptionsBase, + BrandTypographyOptionsGenericText, +): + model_config = ConfigDict(extra="forbid") + + size: str = None + + +class BrandTypographyOptionsNoSize( + BrandTypographyOptionsBase, + BrandTypographyOptionsGenericText, +): + model_config = ConfigDict(extra="forbid") + + +class BrandTypographyEmphasis(BrandTypographyOptionsBase): + model_config = ConfigDict(extra="forbid") + + +class BrandTypographyLink(BrandTypographyOptionsBase): + model_config = ConfigDict(extra="forbid") + + decoration: str = None + + +class BrandTypography(BaseModel): + font: list[ + Annotated[ + Union[ + Annotated[BrandTypographyFontGoogle, Tag("google")], + Annotated[BrandTypographyFontFile, Tag("file")], + ], + Discriminator(brand_typography_font_discriminator), + ] + ] = None + base: BrandTypographyOptions = None + headings: BrandTypographyOptionsNoSize = None + monospace: BrandTypographyOptions = None + emphasis: BrandTypographyEmphasis = None + link: BrandTypographyLink = None diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py new file mode 100644 index 00000000..ff744a13 --- /dev/null +++ b/pkg-py/tests/test_typography.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +import pytest + +from brand_yaml.typography import BrandTypographyFontFile + + +@pytest.mark.parametrize( + "source, fmt", + [ + ("my-font.otf", "opentype"), + ("my-font.ttf", "truetype"), + ("my-font.woff", "woff"), + ("my-font.woff2", "woff2"), + ], +) +def test_brand_typography_font_file_format(source, fmt): + font = BrandTypographyFontFile(source=source) + + assert font.source == source + assert font.format == fmt + + +def test_brand_typography_font_file_format_ignored(): + # ignores user-provided formats, uses `source` field + BrandTypographyFontFile(source="my-font.otf", format="invalid") + + +def test_brand_typography_font_file_weight(): + src = "my-font.otf" + + with pytest.raises(ValueError): + BrandTypographyFontFile(source=src, weight="invalid") + + with pytest.raises(ValueError): + BrandTypographyFontFile(source=src, weight=999) + + with pytest.raises(ValueError): + BrandTypographyFontFile(source=src, weight=150) + + with pytest.raises(ValueError): + BrandTypographyFontFile(source=src, weight=0) + + assert BrandTypographyFontFile(source=src, weight=100).weight == 100 + assert BrandTypographyFontFile(source=src, weight="thin").weight == 100 + assert BrandTypographyFontFile(source=src, weight="semi-bold").weight == 600 + assert BrandTypographyFontFile(source=src, weight="bold").weight == "bold" + assert ( + BrandTypographyFontFile(source=src, weight="normal").weight == "normal" + ) From fdb0b81d02fef166d70a2a646e332a44415e2d81 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 6 Sep 2024 16:55:58 -0400 Subject: [PATCH 037/119] chore: Suppress unused fields in `repr()` methods --- pkg-py/src/brand_yaml/_utils.py | 10 ++++++++++ pkg-py/src/brand_yaml/color.py | 3 ++- pkg-py/src/brand_yaml/meta.py | 10 ++++++---- pkg-py/src/brand_yaml/typography.py | 16 +++++++++------- 4 files changed, 27 insertions(+), 12 deletions(-) create mode 100644 pkg-py/src/brand_yaml/_utils.py diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_utils.py new file mode 100644 index 00000000..d92dbb99 --- /dev/null +++ b/pkg-py/src/brand_yaml/_utils.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from pydantic import BaseModel + + +class BrandBase(BaseModel): + def __repr_args__(self): + fields = [f for f in self.model_fields.keys()] + values = [getattr(self, f) for f in fields] + return ((f, v) for f, v in zip(fields, values) if v is not None) diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 5dbb9301..06018034 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -6,9 +6,10 @@ from pydantic import ConfigDict, Field, model_validator from ._defs import BrandWith, defs_replace_recursively +from ._utils import BrandBase -class BrandColor(BrandWith[str]): +class BrandColor(BrandBase, BrandWith[str]): """ Brand Colors diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index 813b9e83..b360c9d7 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -1,9 +1,11 @@ from __future__ import annotations -from pydantic import BaseModel, ConfigDict, Field, HttpUrl +from pydantic import ConfigDict, Field, HttpUrl +from ._utils import BrandBase -class BrandMeta(BaseModel): + +class BrandMeta(BrandBase): """ Brand metadata is stored in `meta`, providing place to describe the company or project, the brand guidelines, additional links, and more. @@ -23,14 +25,14 @@ class BrandMeta(BaseModel): ) -class BrandMetaName(BaseModel): +class BrandMetaName(BrandBase): model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) full: str = Field(None, examples=["Very Big Corporation of America"]) short: str = Field(None, examples=["VBC"]) -class BrandMetaLink(BaseModel): +class BrandMetaLink(BrandBase): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) home: HttpUrl = Field( diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 98ca46f1..e9174b9a 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -14,6 +14,8 @@ model_validator, ) +from ._utils import BrandBase + T = TypeVar("T") SingleOrList = Union[T, list[T]] @@ -183,7 +185,7 @@ class BrandNamedColor(RootModel): root: str -class BrandTypographyOptionsBase(BaseModel): +class BrandTypographyOptionsBase(BrandBase): model_config = ConfigDict(populate_by_name=True) weight: BrandTypographyFontWeightSimpleType = None @@ -195,11 +197,6 @@ class BrandTypographyOptionsBase(BaseModel): def validate_weight(cls, value: int | str): return validate_font_weight(value) - def __repr_args__(self): - fields = [f for f in self.model_fields.keys()] - values = [getattr(self, f) for f in fields] - return ((f, v) for f, v in zip(fields, values) if v is not None) - class BrandTypographyOptionsGenericText(BaseModel): model_config = ConfigDict(populate_by_name=True) @@ -235,7 +232,7 @@ class BrandTypographyLink(BrandTypographyOptionsBase): decoration: str = None -class BrandTypography(BaseModel): +class BrandTypography(BrandBase): font: list[ Annotated[ Union[ @@ -250,3 +247,8 @@ class BrandTypography(BaseModel): monospace: BrandTypographyOptions = None emphasis: BrandTypographyEmphasis = None link: BrandTypographyLink = None + + def __repr_args__(self): + fields = [f for f in self.model_fields.keys()] + values = [getattr(self, f) for f in fields] + return ((f, v) for f, v in zip(fields, values) if v is not None) From e962daf5785edd027176734fb9e3c3dbcd1e6814 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 6 Sep 2024 16:58:39 -0400 Subject: [PATCH 038/119] tests(BrandTypographyFrontFile): `family` is required --- pkg-py/tests/test_typography.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index ff744a13..f0e9059e 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -15,7 +15,7 @@ ], ) def test_brand_typography_font_file_format(source, fmt): - font = BrandTypographyFontFile(source=source) + font = BrandTypographyFontFile(source=source, family="My Font") assert font.source == source assert font.format == fmt @@ -23,28 +23,30 @@ def test_brand_typography_font_file_format(source, fmt): def test_brand_typography_font_file_format_ignored(): # ignores user-provided formats, uses `source` field - BrandTypographyFontFile(source="my-font.otf", format="invalid") + BrandTypographyFontFile( + source="my-font.otf", + family="My Font", + format="invalid", + ) def test_brand_typography_font_file_weight(): - src = "my-font.otf" + args = {"source": "my-font.otf", "family": "My Font"} with pytest.raises(ValueError): - BrandTypographyFontFile(source=src, weight="invalid") + BrandTypographyFontFile(**args, weight="invalid") with pytest.raises(ValueError): - BrandTypographyFontFile(source=src, weight=999) + BrandTypographyFontFile(**args, weight=999) with pytest.raises(ValueError): - BrandTypographyFontFile(source=src, weight=150) + BrandTypographyFontFile(**args, weight=150) with pytest.raises(ValueError): - BrandTypographyFontFile(source=src, weight=0) - - assert BrandTypographyFontFile(source=src, weight=100).weight == 100 - assert BrandTypographyFontFile(source=src, weight="thin").weight == 100 - assert BrandTypographyFontFile(source=src, weight="semi-bold").weight == 600 - assert BrandTypographyFontFile(source=src, weight="bold").weight == "bold" - assert ( - BrandTypographyFontFile(source=src, weight="normal").weight == "normal" - ) + BrandTypographyFontFile(**args, weight=0) + + assert BrandTypographyFontFile(**args, weight=100).weight == 100 + assert BrandTypographyFontFile(**args, weight="thin").weight == 100 + assert BrandTypographyFontFile(**args, weight="semi-bold").weight == 600 + assert BrandTypographyFontFile(**args, weight="bold").weight == "bold" + assert BrandTypographyFontFile(**args, weight="normal").weight == "normal" From 7c24436cd592a2ee6861f6ee42f8b5d3295bfea6 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Mon, 9 Sep 2024 16:56:33 -0400 Subject: [PATCH 039/119] chore(pyright): pyright is now happy --- .gitignore | 1 + pkg-py/pyproject.toml | 6 +- pkg-py/src/brand_yaml/__init__.py | 14 ++-- pkg-py/src/brand_yaml/_defs.py | 45 ++++++----- pkg-py/src/brand_yaml/logo.py | 6 +- pkg-py/src/brand_yaml/meta.py | 20 ++--- pkg-py/src/brand_yaml/typography.py | 70 +++++++++++------- pkg-py/tests/test_color.py | 5 ++ pkg-py/tests/test_defs.py | 111 +++++++++++++++------------- pkg-py/tests/test_logo.py | 23 ++++-- pkg-py/tests/test_meta.py | 66 +++++++++++------ pkg-py/tests/test_typography.py | 47 ++++++++---- pkg-py/uv.lock | 56 +++++++++++--- 13 files changed, 300 insertions(+), 170 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ed8ebf58 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/pkg-py/pyproject.toml b/pkg-py/pyproject.toml index 041b1daf..bad50dea 100644 --- a/pkg-py/pyproject.toml +++ b/pkg-py/pyproject.toml @@ -18,6 +18,7 @@ quarto = [ "nbclient>=0.10.0", ] dev = [ + "pyright>=1.1.379", "pytest>=8.3.2", ] @@ -25,6 +26,9 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.pyright] +exclude = ["_dev", ".venv", "src/brand_yaml/_brand.py"] + [tool.ruff] exclude = [ ".bzr", @@ -77,4 +81,4 @@ indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" docstring-code-format = true -docstring-code-line-length = "dynamic" \ No newline at end of file +docstring-code-line-length = "dynamic" diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index f86d312c..f2fc7942 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -1,9 +1,9 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Union +from typing import Any -from pydantic import BaseModel, ConfigDict +from pydantic import BaseModel, ConfigDict, Field from ruamel.yaml import YAML from .color import BrandColor @@ -21,11 +21,11 @@ class Brand(BaseModel): validate_assignment=True, ) - meta: BrandMeta = None - logo: Union[str, BrandLogo] = None - color: BrandColor = None - typography: BrandTypography = None - defaults: dict[str, Any] = None + meta: BrandMeta | None = Field(None) + logo: str | BrandLogo | None = Field(None) + color: BrandColor | None = Field(None) + typography: BrandTypography | None = Field(None) + defaults: dict[str, Any] | None = Field(None) def read_brand_yaml(path: str | Path) -> Brand: diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index b1faf573..30492846 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -2,7 +2,7 @@ from copy import deepcopy from textwrap import indent -from typing import Any, Generic, Optional, TypeVar, Union +from typing import Any, Generic, Iterable, TypeVar, Union from pydantic import ( BaseModel, @@ -14,14 +14,18 @@ from ._utils_logging import logger +DictString = dict[str, str] +DictStringRecursive = Union[DictString, dict[str, "DictStringRecursive"]] +DictStringRecursiveBaseModel = Union[DictStringRecursive, dict[str, BaseModel]] + T = TypeVar("T") class BrandLightDark(BaseModel, Generic[T]): model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) - light: T = None - dark: T = None + light: T | None = None + dark: T | None = None class BrandLightDarkString(BrandLightDark[str]): @@ -49,7 +53,7 @@ class BrandWith(BaseModel, Generic[T]): validate_assignment=True, ) - with_: Optional[dict[str, T]] = Field(default=None, alias="with") + with_: dict[str, T] | None = Field(default=None, alias="with") @field_validator("with_", mode="after") @classmethod @@ -73,7 +77,9 @@ def resolve_with_values(self): return self -def defs_get(defs: dict[str, object], key: str, level: int = 0) -> object: +def defs_get( + defs: DictStringRecursiveBaseModel, key: str, level: int = 0 +) -> object: """ Finds `key` in `with_`, which may require recursively resolving nested values from `with_`. @@ -105,18 +111,18 @@ def defs_get(defs: dict[str, object], key: str, level: int = 0) -> object: level_indent(f"key {key} is in with_ with value {with_value!r}", level) ) - if is_leaf_node(defs[key]): + if isinstance(with_value, (dict, BaseModel)): + defs_replace_recursively(defs, with_value, level=level) return with_value else: - defs_replace_recursively(defs, with_value, level=level) return with_value def defs_replace_recursively( - defs: dict[str, object], - items: dict | BaseModel = None, + defs: Any, + items: dict | BaseModel | None = None, level: int = 0, - name: str = None, + name: str | None = None, ): """ Recursively replace string values in `items` with their definition in @@ -190,7 +196,7 @@ def level_indent(x: str, level: int) -> str: return indent(x, ("." * level)) -def item_keys(item: dict | BaseModel) -> list[str]: +def item_keys(item: DictStringRecursiveBaseModel | BaseModel) -> Iterable[str]: if isinstance(item, BaseModel): return item.model_fields.keys() elif hasattr(item, "keys"): @@ -207,11 +213,11 @@ def get_value(items: dict | BaseModel, key: str) -> object: def check_circular_references( - data: dict[str, object], - current: object = None, - seen: list[str] = None, - path: list[str] = None, - name: str = None, + data: Any, + current: object | None = None, + seen: list[str] | None = None, + path: list[str] | None = None, + name: str | None = None, ): current = current if current is not None else data seen = seen if seen is not None else [] @@ -252,7 +258,12 @@ def check_circular_references( class CircularReferenceError(Exception): - def __init__(self, seen: list[str], path: list[str], name: str = None): + def __init__( + self, + seen: list[str], + path: list[str], + name: str | None = None, + ): self.seen = seen self.path = path self.name = name diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 842bbc87..a98762ae 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -38,6 +38,6 @@ class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): # TODO: Currently we're using a string for the logo path, but we should # update this to use a validated Path or URL in the future. - small: str | BrandLightDark[str] = None - medium: str | BrandLightDark[str] = None - large: str | BrandLightDark[str] = None + small: str | BrandLightDark[str] | None = None + medium: str | BrandLightDark[str] | None = None + large: str | BrandLightDark[str] | None = None diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index b360c9d7..94903ead 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -13,10 +13,10 @@ class BrandMeta(BrandBase): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) - name: str | BrandMetaName = Field( + name: str | BrandMetaName | None = Field( None, examples=["Very Big Corporation of America"] ) - link: HttpUrl | BrandMetaLink = Field( + link: HttpUrl | BrandMetaLink | None = Field( None, examples=[ "https://very-big-corp.com", @@ -28,34 +28,34 @@ class BrandMeta(BrandBase): class BrandMetaName(BrandBase): model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) - full: str = Field(None, examples=["Very Big Corporation of America"]) - short: str = Field(None, examples=["VBC"]) + full: str | None = Field(None, examples=["Very Big Corporation of America"]) + short: str | None = Field(None, examples=["VBC"]) class BrandMetaLink(BrandBase): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) - home: HttpUrl = Field( + home: HttpUrl | None = Field( None, examples=["https://very-big-corp.com"], ) - mastodon: HttpUrl = Field( + mastodon: HttpUrl | None = Field( None, examples=["https://mastodon.social/@VeryBigCorpOfficial"], ) - github: HttpUrl = Field( + github: HttpUrl | None = Field( None, examples=["https://github.com/Very-Big-Corp"], ) - linkedin: HttpUrl = Field( + linkedin: HttpUrl | None = Field( None, examples=["https://linkedin.com/company/very-big-corp"], ) - twitter: HttpUrl = Field( + twitter: HttpUrl | None = Field( None, examples=["https://twitter.com/VeryBigCorp"], ) - facebook: HttpUrl = Field( + facebook: HttpUrl | None = Field( None, examples=["https://facebook.com/Very-Big-Corp"], ) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index e9174b9a..9f3728a6 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Annotated, Literal, TypeVar, Union +from typing import Annotated, Any, Literal, TypeVar, Union from pydantic import ( BaseModel, @@ -77,7 +77,7 @@ class BrandInvalidFontWeight(ValueError): - def __init__(self, value: any): + def __init__(self, value: Any): super().__init__( f"Invalid font weight {value!r}. Expected a number divisible " + "by 100 and between 100 and 900, or one of " @@ -86,7 +86,7 @@ def __init__(self, value: any): class BrandUnsupportedFontFileFormat(ValueError): - def __init__(self, value: any): + def __init__(self, value: Any): supported = ("opentype", "truetype", "woff", "woff2") super().__init__( f"Unsupported font file {value!r}. Expected one of {', '.join(supported)}." @@ -118,8 +118,6 @@ class BrandTypographyFontFile(BaseModel): source: str family: str - # These are the formats supported by brand_yaml, not *all* available formats - format: Literal["opentype", "truetype", "woff", "woff2"] = None weight: BrandTypographyFontWeightSimpleType = "normal" style: BrandTypographyFontStyleType = "normal" @@ -128,17 +126,29 @@ class BrandTypographyFontFile(BaseModel): def validate_weight(cls, value: int | str): return validate_font_weight(value) - @model_validator(mode="before") + @field_validator("source", mode="after") @classmethod - def validate_format(cls, data: any): - source = data["source"] - if Path(source).suffix not in FontFormats: - raise BrandUnsupportedFontFileFormat(source) + def validate_source(cls, value: str): + if not Path(value).suffix: + raise BrandUnsupportedFontFileFormat(value) - # Get file extension of `source` using built-in modules - data["format"] = FontFormats[Path(data["source"]).suffix] + if Path(value).suffix not in FontFormats: + raise BrandUnsupportedFontFileFormat(value) - return data + return value + + @property + def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: + source_ext = Path(self.source).suffix + + if source_ext not in FontFormats: + raise BrandUnsupportedFontFileFormat(self.source) + + fmt = FontFormats[source_ext] + if fmt not in ("opentype", "truetype", "woff", "woff2"): + raise BrandUnsupportedFontFileFormat(self.source) + + return fmt class BrandTypographyFontGoogle(BaseModel): @@ -188,9 +198,11 @@ class BrandNamedColor(RootModel): class BrandTypographyOptionsBase(BrandBase): model_config = ConfigDict(populate_by_name=True) - weight: BrandTypographyFontWeightSimpleType = None - color: BrandNamedColor = None - background_color: BrandNamedColor = Field(None, alias="background-color") + weight: BrandTypographyFontWeightSimpleType | None = None + color: BrandNamedColor | None = None + background_color: BrandNamedColor | None = Field( + None, alias="background-color" + ) @field_validator("weight", mode="before") @classmethod @@ -201,9 +213,9 @@ def validate_weight(cls, value: int | str): class BrandTypographyOptionsGenericText(BaseModel): model_config = ConfigDict(populate_by_name=True) - family: str = None - line_height: float = Field(None, alias="line-height") - style: SingleOrList[BrandTypographyFontStyleType] = None + family: str | None = None + line_height: float | None = Field(None, alias="line-height") + style: SingleOrList[BrandTypographyFontStyleType] | None = None class BrandTypographyOptions( @@ -212,7 +224,7 @@ class BrandTypographyOptions( ): model_config = ConfigDict(extra="forbid") - size: str = None + size: str | None = None class BrandTypographyOptionsNoSize( @@ -229,11 +241,13 @@ class BrandTypographyEmphasis(BrandTypographyOptionsBase): class BrandTypographyLink(BrandTypographyOptionsBase): model_config = ConfigDict(extra="forbid") - decoration: str = None + decoration: str | None = None class BrandTypography(BrandBase): - font: list[ + model_config = ConfigDict(extra="forbid") + + fonts: list[ Annotated[ Union[ Annotated[BrandTypographyFontGoogle, Tag("google")], @@ -241,12 +255,12 @@ class BrandTypography(BrandBase): ], Discriminator(brand_typography_font_discriminator), ] - ] = None - base: BrandTypographyOptions = None - headings: BrandTypographyOptionsNoSize = None - monospace: BrandTypographyOptions = None - emphasis: BrandTypographyEmphasis = None - link: BrandTypographyLink = None + ] = Field(default_factory=list) + base: BrandTypographyOptions | None = None + headings: BrandTypographyOptionsNoSize | None = None + monospace: BrandTypographyOptions | None = None + emphasis: BrandTypographyEmphasis | None = None + link: BrandTypographyLink | None = None def __repr_args__(self): fields = [f for f in self.model_fields.keys()] diff --git a/pkg-py/tests/test_color.py b/pkg-py/tests/test_color.py index a2d4b049..ad837425 100644 --- a/pkg-py/tests/test_color.py +++ b/pkg-py/tests/test_color.py @@ -8,6 +8,7 @@ def test_brand_color_posit_direct(): brand = read_brand_yaml(path_examples("brand-color-posit-direct.yml")) + assert brand.color is not None assert brand.color.foreground == "#151515" assert brand.color.background == "#FFFFFF" assert brand.color.primary == "#447099" @@ -25,6 +26,7 @@ def test_brand_color_posit_with(): brand = read_brand_yaml(path_examples("brand-color-posit-with.yml")) # Same final values as above, but re-uses color definitions from `with` + assert brand.color is not None assert brand.color.foreground == "#151515" assert brand.color.background == "#FFFFFF" assert brand.color.primary == "#447099" @@ -37,6 +39,7 @@ def test_brand_color_posit_with(): assert brand.color.light == "#FFFFFF" assert brand.color.dark == "#404041" + assert brand.color.with_ is not None assert brand.color.with_ == { "white": "#FFFFFF", "black": "#151515", @@ -52,11 +55,13 @@ def test_brand_color_posit_internal(): brand = read_brand_yaml(path_examples("brand-color-posit-internal.yml")) # Named theme colors are reused in BrandColor + assert brand.color is not None assert brand.color.background == "#FFFFFF" assert brand.color.primary == "#447099" assert brand.color.info == brand.color.primary assert brand.color.light == brand.color.background + assert brand.color.with_ is not None assert brand.color.with_ == { "white": "#FFFFFF", "black": "#151515", diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index 34ae71af..63c2d8a8 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -16,15 +16,17 @@ def test_brand_with_simple(): class BrandThing(BrandWith[str]): - small: str = None - medium: str = None - large: str = None - - thing = BrandThing( - with_={"sm": "small", "md": "medium", "lg": "large"}, - small="sm", - medium="md", - large="lg", + small: str | None = None + medium: str | None = None + large: str | None = None + + thing = BrandThing.model_validate( + { + "with_": {"sm": "small", "md": "medium", "lg": "large"}, + "small": "sm", + "medium": "md", + "large": "lg", + } ) assert thing.small == "small" @@ -34,15 +36,17 @@ class BrandThing(BrandWith[str]): def test_brand_with_simple_mixed(): class BrandThing(BrandWith[str]): - small: str = None - medium: str = None - large: str = None - - thing = BrandThing( - with_={"sm": "small", "md": "medium", "lg": "large"}, - small="lg", - medium="middle", - large="LG", + small: str | None = None + medium: str | None = None + large: str | None = None + + thing = BrandThing.model_validate( + { + "with_": {"sm": "small", "md": "medium", "lg": "large"}, + "small": "lg", + "medium": "middle", + "large": "LG", + } ) assert thing.small == "large" @@ -52,13 +56,15 @@ class BrandThing(BrandWith[str]): def test_brand_with_dict(): class BrandThing(BrandWith[str]): - small: dict[str, str] = None - medium: dict[str, str] = None - - thing = BrandThing( - with_={"sm": "small", "md": "medium", "lg": "large"}, - small={"one": "sm", "two": "md"}, - medium={"three": "md"}, + small: dict[str, str] | None = None + medium: dict[str, str] | None = None + + thing = BrandThing.model_validate( + { + "with_": {"sm": "small", "md": "medium", "lg": "large"}, + "small": {"one": "sm", "two": "md"}, + "medium": {"three": "md"}, + } ) assert thing.small == {"one": "small", "two": "medium"} @@ -67,47 +73,52 @@ class BrandThing(BrandWith[str]): def test_brand_with_basemodel(): class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): - small: Union[str, BrandLightDarkString] = None - medium: Union[str, BrandLightDarkString] = None - - thing = BrandThing( - with_={ - "sm": "small-light", - "md": {"light": "medium-light", "dark": "medium-dark"}, - }, - small={"light": "sm", "dark": "small-dark"}, - medium="md", + small: str | BrandLightDarkString | None = None + medium: str | BrandLightDarkString | None = None + + thing = BrandThing.model_validate( + { + "with_": { + "sm": "small-light", + "md": {"light": "medium-light", "dark": "medium-dark"}, + }, + "small": {"light": "sm", "dark": "small-dark"}, + "medium": "md", + } ) + assert isinstance(thing.small, BrandLightDarkString) + assert isinstance(thing.medium, BrandLightDarkString) + assert thing.small.light == "small-light" assert thing.small.dark == "small-dark" assert thing.medium.light == "medium-light" assert thing.medium.dark == "medium-dark" - assert isinstance(thing.small, BrandLightDarkString) - assert isinstance(thing.medium, BrandLightDarkString) - def test_brand_with_nested(): class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): - the: Union[str, BrandLightDarkString] = None - - thing = BrandThing( - with_={ - "light": "the-light", - "dark": "the-dark", - "both": {"light": "the-light", "dark": "the-dark"}, - }, - the="both", + the: str | BrandLightDarkString | None = None + + thing = BrandThing.model_validate( + { + "with_": { + "light": "the-light", + "dark": "the-dark", + "both": {"light": "the-light", "dark": "the-dark"}, + }, + "the": "both", + } ) + assert thing.with_ is not None + assert isinstance(thing.with_["both"], BrandLightDarkString) + assert isinstance(thing.the, BrandLightDarkString) + assert thing.the == thing.with_["both"] assert thing.with_["both"].light == "the-light" assert thing.with_["both"].dark == "the-dark" - assert isinstance(thing.with_["both"], BrandLightDarkString) - assert isinstance(thing.the, BrandLightDarkString) - def test_brand_with_errors_on_circular_references(): with pytest.raises(CircularReferenceError, match="a -> b -> a"): diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index be1f7178..df8d7410 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -3,6 +3,8 @@ from utils import path_examples from brand_yaml import read_brand_yaml +from brand_yaml._defs import BrandLightDark +from brand_yaml.logo import BrandLogo def test_brand_logo_single(): @@ -14,6 +16,7 @@ def test_brand_logo_single(): def test_brand_logo_simple(): brand = read_brand_yaml(path_examples("brand-logo-simple.yml")) + assert isinstance(brand.logo, BrandLogo) assert brand.logo.small == "icon.png" assert brand.logo.medium == "logo.png" assert brand.logo.large == "display.svg" @@ -22,22 +25,28 @@ def test_brand_logo_simple(): def test_brand_logo_light_dark(): brand = read_brand_yaml(path_examples("brand-logo-light-dark.yml")) + assert isinstance(brand.logo, BrandLogo) assert brand.logo.small == "icon.png" + + assert isinstance(brand.logo.medium, BrandLightDark) assert brand.logo.medium.light == "logo-light.png" assert brand.logo.medium.dark == "logo-dark.png" + assert brand.logo.large == "display.svg" def test_brand_logo_full(): - brand_logo_full = read_brand_yaml(path_examples("brand-logo-full.yml")) + brand = read_brand_yaml(path_examples("brand-logo-full.yml")) - assert brand_logo_full.logo.small == "favicon.png" + assert isinstance(brand.logo, BrandLogo) + assert brand.logo.small == "favicon.png" - assert brand_logo_full.logo.medium.light == "full-color.png" - assert brand_logo_full.logo.medium.dark == "full-color-reverse.png" + assert isinstance(brand.logo.medium, BrandLightDark) + assert brand.logo.medium.light == "full-color.png" + assert brand.logo.medium.dark == "full-color-reverse.png" - assert brand_logo_full.logo.large == "full-color.svg" + assert brand.logo.large == "full-color.svg" # replace small with new value from "with" - brand_logo_full.logo.small = "black" - assert brand_logo_full.logo.small == "black.png" + brand.logo.small = "black" + assert brand.logo.small == "black.png" diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index 606d4ef6..1c098a83 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -1,33 +1,46 @@ from __future__ import annotations import pytest -from pydantic import HttpUrl from utils import path_examples from brand_yaml import read_brand_yaml -from brand_yaml.meta import BrandMeta +from brand_yaml.meta import BrandMeta, BrandMetaLink def test_brand_meta(): - meta = BrandMeta( - name={"full": "Very Big Corporation of America ", "short": " VBC "}, - link={"home": "https://very-big-corp.com"}, + meta = BrandMeta.model_validate( + { + "name": { + "full": "Very Big Corporation of America ", + "short": " VBC ", + }, + "link": {"home": "https://very-big-corp.com"}, + } ) + + assert meta.name is not None + assert not isinstance(meta.name, str) assert meta.name.full == "Very Big Corporation of America" assert meta.name.short == "VBC" - assert meta.link.home == HttpUrl("https://very-big-corp.com/") + + assert meta.link is not None + assert isinstance(meta.link, BrandMetaLink) + assert str(meta.link.home) == "https://very-big-corp.com/" def test_brand_meta_empty(): - meta = BrandMeta() + meta = BrandMeta(name=None, link=None) assert meta.name is None assert meta.link is None - meta_empty_name = BrandMeta(link="https://example.com") + meta_empty_name = BrandMeta(name=None, link="https://example.com") # type: ignore assert meta_empty_name.name is None - assert meta_empty_name.link == HttpUrl("https://example.com") + assert str(meta_empty_name.link) == "https://example.com/" - meta_empty_link = BrandMeta(name="Very Big Corporation of America") + meta_empty_link = BrandMeta( + name="Very Big Corporation of America", + link=None, + ) assert meta_empty_link.name == "Very Big Corporation of America" assert meta_empty_link.link is None @@ -35,32 +48,39 @@ def test_brand_meta_empty(): def test_brand_meta_bad_url(): with pytest.raises(ValueError): BrandMeta( - name={"full": "Very Big Corporation of America ", "short": " VBC "}, - link={"home": "not-a-url"}, + name={"full": "Very Big Corporation of America ", "short": " VBC "}, # type: ignore + link={"home": "not-a-url"}, # type: ignore ) def test_brand_meta_yaml_full(): brand = read_brand_yaml(path_examples("brand-meta-full.yml")) + assert brand.meta is not None + assert brand.meta.name is not None + assert not isinstance(brand.meta.name, str) assert brand.meta.name.full == "Very Big Corporation of America" assert brand.meta.name.short == "VBC" - assert brand.meta.link.home == HttpUrl("https://very-big-corp.com") - assert brand.meta.link.mastodon == HttpUrl( - "https://mastodon.social/@VeryBigCorpOfficial" - ) - assert brand.meta.link.github == HttpUrl("https://github.com/Very-Big-Corp") - assert brand.meta.link.linkedin == HttpUrl( - "https://linkedin.com/company/very-big-corp" + + assert brand.meta.link is not None + assert isinstance(brand.meta.link, BrandMetaLink) + assert str(brand.meta.link.home) == "https://very-big-corp.com/" + assert ( + str(brand.meta.link.mastodon) + == "https://mastodon.social/@VeryBigCorpOfficial" ) - assert brand.meta.link.twitter == HttpUrl("https://twitter.com/VeryBigCorp") - assert brand.meta.link.facebook == HttpUrl( - "https://facebook.com/Very-Big-Corp" + assert str(brand.meta.link.github) == "https://github.com/Very-Big-Corp" + assert ( + str(brand.meta.link.linkedin) + == "https://linkedin.com/company/very-big-corp" ) + assert str(brand.meta.link.twitter) == "https://twitter.com/VeryBigCorp" + assert str(brand.meta.link.facebook) == "https://facebook.com/Very-Big-Corp" def test_brand_meta_yaml_small(): brand = read_brand_yaml(path_examples("brand-meta-small.yml")) + assert brand.meta is not None assert brand.meta.name == "Very Big Corp. of America" - assert brand.meta.link == HttpUrl("https://very-big-corp.com") + assert str(brand.meta.link) == "https://very-big-corp.com/" diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index f0e9059e..8682ddea 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -23,10 +23,8 @@ def test_brand_typography_font_file_format(source, fmt): def test_brand_typography_font_file_format_ignored(): # ignores user-provided formats, uses `source` field - BrandTypographyFontFile( - source="my-font.otf", - family="My Font", - format="invalid", + BrandTypographyFontFile.model_validate( + {"source": "my-font.otf", "family": "My Font"} ) @@ -34,19 +32,42 @@ def test_brand_typography_font_file_weight(): args = {"source": "my-font.otf", "family": "My Font"} with pytest.raises(ValueError): - BrandTypographyFontFile(**args, weight="invalid") + BrandTypographyFontFile.model_validate({**args, "weight": "invalid"}) with pytest.raises(ValueError): - BrandTypographyFontFile(**args, weight=999) + BrandTypographyFontFile.model_validate({**args, "weight": 999}) with pytest.raises(ValueError): - BrandTypographyFontFile(**args, weight=150) + BrandTypographyFontFile.model_validate({**args, "weight": 150}) with pytest.raises(ValueError): - BrandTypographyFontFile(**args, weight=0) + BrandTypographyFontFile.model_validate({**args, "weight": 0}) - assert BrandTypographyFontFile(**args, weight=100).weight == 100 - assert BrandTypographyFontFile(**args, weight="thin").weight == 100 - assert BrandTypographyFontFile(**args, weight="semi-bold").weight == 600 - assert BrandTypographyFontFile(**args, weight="bold").weight == "bold" - assert BrandTypographyFontFile(**args, weight="normal").weight == "normal" + assert ( + BrandTypographyFontFile.model_validate({**args, "weight": 100}).weight + == 100 + ) + assert ( + BrandTypographyFontFile.model_validate( + {**args, "weight": "thin"} + ).weight + == 100 + ) + assert ( + BrandTypographyFontFile.model_validate( + {**args, "weight": "semi-bold"} + ).weight + == 600 + ) + assert ( + BrandTypographyFontFile.model_validate( + {**args, "weight": "bold"} + ).weight + == "bold" + ) + assert ( + BrandTypographyFontFile.model_validate( + {**args, "weight": "normal"} + ).weight + == "normal" + ) diff --git a/pkg-py/uv.lock b/pkg-py/uv.lock index 314172ad..69518305 100644 --- a/pkg-py/uv.lock +++ b/pkg-py/uv.lock @@ -1,17 +1,28 @@ version = 1 requires-python = ">=3.9" resolution-markers = [ - "python_full_version < '3.10'", - "python_full_version < '3.11'", - "python_full_version < '3.12'", - "python_full_version < '3.13'", - "python_full_version < '3.10'", - "python_full_version == '3.10.*'", - "python_full_version == '3.11.*'", - "python_full_version == '3.12.*'", - "python_full_version < '3.13'", - "python_full_version >= '3.13' and python_full_version < '4.0'", - "python_full_version >= '4.0'", + "python_full_version < '3.10' and platform_system != 'Emscripten'", + "python_full_version < '3.10' and platform_system == 'Emscripten'", + "python_full_version < '3.11' and platform_system != 'Emscripten'", + "python_full_version < '3.11' and platform_system == 'Emscripten'", + "python_full_version < '3.12' and platform_system != 'Emscripten'", + "python_full_version < '3.12' and platform_system == 'Emscripten'", + "python_full_version < '3.13' and platform_system != 'Emscripten'", + "python_full_version < '3.13' and platform_system == 'Emscripten'", + "python_full_version < '3.10' and platform_system != 'Emscripten'", + "python_full_version < '3.10' and platform_system == 'Emscripten'", + "python_full_version == '3.10.*' and platform_system != 'Emscripten'", + "python_full_version == '3.10.*' and platform_system == 'Emscripten'", + "python_full_version == '3.11.*' and platform_system != 'Emscripten'", + "python_full_version == '3.11.*' and platform_system == 'Emscripten'", + "python_full_version == '3.12.*' and platform_system != 'Emscripten'", + "python_full_version == '3.12.*' and platform_system == 'Emscripten'", + "python_full_version < '3.13' and platform_system != 'Emscripten'", + "python_full_version < '3.13' and platform_system == 'Emscripten'", + "python_full_version >= '3.13' and python_full_version < '4.0' and platform_system != 'Emscripten'", + "python_full_version >= '3.13' and python_full_version < '4.0' and platform_system == 'Emscripten'", + "python_full_version >= '4.0' and platform_system != 'Emscripten'", + "python_full_version >= '4.0' and platform_system == 'Emscripten'", ] [[package]] @@ -45,6 +56,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "pyright" }, { name = "pytest" }, ] quarto = [ @@ -60,6 +72,7 @@ requires-dist = [ { name = "nbclient", marker = "extra == 'quarto'", specifier = ">=0.10.0" }, { name = "nbformat", marker = "extra == 'quarto'", specifier = ">=5.10.4" }, { name = "pydantic", specifier = ">=2.8.2" }, + { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.379" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.2" }, { name = "pyyaml", marker = "extra == 'quarto'", specifier = ">=6.0.2" }, { name = "ruamel-yaml", specifier = ">=0.18.6" }, @@ -279,6 +292,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 }, +] + [[package]] name = "packaging" version = "24.1" @@ -416,6 +438,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/8b/30233f741e16b35499fa2fad2f4a69eb127eec6c850a1b14af26e7b08b73/pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7", size = 1915399 }, ] +[[package]] +name = "pyright" +version = "1.1.379" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "nodeenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/62/fd6f13c417087b8e941c3d7cbc37fcf5ec62b6ae7031837194c0926cb704/pyright-1.1.379.tar.gz", hash = "sha256:6f426cb6443786fa966b930c23ad1941c8cb9fe672e4589daea8d80bb34193ea", size = 17481 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/4b/98bb2d6eb98ca35fa5fe90d787c3181310ed153c996af3a7f158487b9a87/pyright-1.1.379-py3-none-any.whl", hash = "sha256:01954811ac71db8646f50de1577576dc275ffb891a9e7324350e676cf6df323f", size = 18221 }, +] + [[package]] name = "pytest" version = "8.3.2" From 18950e97add45140e464d45c5b10372f7acc111d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 10 Sep 2024 16:30:06 -0400 Subject: [PATCH 040/119] feat(typography.monospace): Update monospace properties For #16 --- pkg-py/src/brand_yaml/typography.py | 102 +++++++++++++++++++++++----- pkg-py/tests/test_typography.py | 26 ++++++- 2 files changed, 109 insertions(+), 19 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 9f3728a6..c43bbc03 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -195,57 +195,94 @@ class BrandNamedColor(RootModel): root: str -class BrandTypographyOptionsBase(BrandBase): +class BrandTypographyOptionsColor(BaseModel): model_config = ConfigDict(populate_by_name=True) - weight: BrandTypographyFontWeightSimpleType | None = None color: BrandNamedColor | None = None background_color: BrandNamedColor | None = Field( None, alias="background-color" ) + +class BrandTypographyOptionsWeight(BaseModel): + weight: BrandTypographyFontWeightSimpleType | None = None + @field_validator("weight", mode="before") @classmethod def validate_weight(cls, value: int | str): return validate_font_weight(value) -class BrandTypographyOptionsGenericText(BaseModel): - model_config = ConfigDict(populate_by_name=True) - +class BrandTypographyOptionsGenericText(BrandTypographyOptionsWeight): family: str | None = None - line_height: float | None = Field(None, alias="line-height") style: SingleOrList[BrandTypographyFontStyleType] | None = None -class BrandTypographyOptions( - BrandTypographyOptionsBase, +class BrandTypographyOptionsSize(BaseModel): + size: str | None = None + + +class BrandTypographyOptionsBlockText(BaseModel): + line_height: float | None = Field(None, alias="line-height") + + +class BrandTypographyBase( + BrandBase, BrandTypographyOptionsGenericText, + BrandTypographyOptionsBlockText, + BrandTypographyOptionsColor, ): model_config = ConfigDict(extra="forbid") - size: str | None = None + +class BrandTypographyHeadings( + BrandBase, + BrandTypographyOptionsGenericText, + BrandTypographyOptionsBlockText, + BrandTypographyOptionsColor, +): + model_config = ConfigDict(extra="forbid") -class BrandTypographyOptionsNoSize( - BrandTypographyOptionsBase, +class BrandTypographyMonospace( + BrandBase, BrandTypographyOptionsGenericText, + BrandTypographyOptionsSize, ): model_config = ConfigDict(extra="forbid") -class BrandTypographyEmphasis(BrandTypographyOptionsBase): +class BrandTypographyMonospaceInline( + BrandBase, + BrandTypographyOptionsGenericText, + BrandTypographyOptionsSize, + BrandTypographyOptionsColor, +): model_config = ConfigDict(extra="forbid") -class BrandTypographyLink(BrandTypographyOptionsBase): +class BrandTypographyMonospaceBlock( + BrandBase, + BrandTypographyOptionsGenericText, + BrandTypographyOptionsSize, + BrandTypographyOptionsBlockText, + BrandTypographyOptionsColor, +): + model_config = ConfigDict(extra="forbid") + + +class BrandTypographyLink( + BrandBase, + BrandTypographyOptionsWeight, + BrandTypographyOptionsColor, +): model_config = ConfigDict(extra="forbid") decoration: str | None = None class BrandTypography(BrandBase): - model_config = ConfigDict(extra="forbid") + model_config = ConfigDict(extra="forbid", populate_by_name=True) fonts: list[ Annotated[ @@ -256,13 +293,42 @@ class BrandTypography(BrandBase): Discriminator(brand_typography_font_discriminator), ] ] = Field(default_factory=list) - base: BrandTypographyOptions | None = None - headings: BrandTypographyOptionsNoSize | None = None - monospace: BrandTypographyOptions | None = None - emphasis: BrandTypographyEmphasis | None = None + base: BrandTypographyBase | None = None + headings: BrandTypographyHeadings | None = None + monospace: BrandTypographyMonospace | None = None + monospace_inline: BrandTypographyMonospaceInline | None = Field( + None, alias="monospace-inline" + ) + monospace_block: BrandTypographyMonospaceBlock | None = Field( + None, alias="monospace-block" + ) link: BrandTypographyLink | None = None def __repr_args__(self): fields = [f for f in self.model_fields.keys()] values = [getattr(self, f) for f in fields] return ((f, v) for f, v in zip(fields, values) if v is not None) + + @model_validator(mode="after") + def forward_monospace_values(self): + """ + Forward values from `monospace` to inline and block variants. + + `monospace-inline` and `monospace-block` both inherit `family`, `style`, + `weight` and `size` from `monospace`. + """ + + def use_fallback(obj: BaseModel | None, parent: BaseModel | None): + if parent is None or obj is None: + return + + for field in ("family", "style", "weight", "size"): + fallback = getattr(parent, field) + if fallback is None: + continue + if getattr(obj, field) is None: + setattr(obj, field, fallback) + + use_fallback(self.monospace_inline, self.monospace) + use_fallback(self.monospace_block, self.monospace) + return self diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 8682ddea..7d01e636 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -2,7 +2,7 @@ import pytest -from brand_yaml.typography import BrandTypographyFontFile +from brand_yaml.typography import BrandTypography, BrandTypographyFontFile @pytest.mark.parametrize( @@ -71,3 +71,27 @@ def test_brand_typography_font_file_weight(): ).weight == "normal" ) + + +def test_brand_typography_monospace(): + bt = BrandTypography.model_validate( + { + "monospace": {"family": "Fira Code", "size": "1.2rem"}, + "monospace-inline": {"size": "0.9rem"}, + "monospace-block": { + "family": "Menlo", + }, + } + ) + + assert bt.monospace is not None + assert bt.monospace.family == "Fira Code" + assert bt.monospace.size == "1.2rem" + + assert bt.monospace_inline is not None + assert bt.monospace_inline.family == "Fira Code" # inherits family + assert bt.monospace_inline.size == "0.9rem" # overrides size + + assert bt.monospace_block is not None + assert bt.monospace_block.family == "Menlo" # overrides family + assert bt.monospace_block.size == "1.2rem" # inherits size From bf3cd2edc33bb7f030e5ffd01eda8812e893f560 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 10 Sep 2024 16:30:57 -0400 Subject: [PATCH 041/119] chore(BrandTypography): Already inherits from BrandBase --- pkg-py/src/brand_yaml/typography.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index c43bbc03..b7dd5e97 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -304,11 +304,6 @@ class BrandTypography(BrandBase): ) link: BrandTypographyLink | None = None - def __repr_args__(self): - fields = [f for f in self.model_fields.keys()] - values = [getattr(self, f) for f in fields] - return ((f, v) for f, v in zip(fields, values) if v is not None) - @model_validator(mode="after") def forward_monospace_values(self): """ From b3c6776b6d8f82a5699e1806d31a3ef22569d24c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 10 Sep 2024 16:45:41 -0400 Subject: [PATCH 042/119] tests(BrandTypography): Ensure typography fields have the correct fields --- pkg-py/src/brand_yaml/typography.py | 1 + pkg-py/tests/test_typography.py | 82 ++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 1 deletion(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index b7dd5e97..1a1d7d5d 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -229,6 +229,7 @@ class BrandTypographyOptionsBlockText(BaseModel): class BrandTypographyBase( BrandBase, BrandTypographyOptionsGenericText, + BrandTypographyOptionsSize, BrandTypographyOptionsBlockText, BrandTypographyOptionsColor, ): diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 7d01e636..be5d7188 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -2,7 +2,16 @@ import pytest -from brand_yaml.typography import BrandTypography, BrandTypographyFontFile +from brand_yaml.typography import ( + BrandTypography, + BrandTypographyBase, + BrandTypographyFontFile, + BrandTypographyHeadings, + BrandTypographyLink, + BrandTypographyMonospace, + BrandTypographyMonospaceBlock, + BrandTypographyMonospaceInline, +) @pytest.mark.parametrize( @@ -95,3 +104,74 @@ def test_brand_typography_monospace(): assert bt.monospace_block is not None assert bt.monospace_block.family == "Menlo" # overrides family assert bt.monospace_block.size == "1.2rem" # inherits size + + +def test_brand_typography_fields_base(): + base_fields = set(BrandTypographyBase.model_fields.keys()) + + assert base_fields == { + "family", + "weight", + "style", + "size", + "line_height", + "color", + "background_color", + } + + +def test_brand_typography_fields_headings(): + headings_fields = set(BrandTypographyHeadings.model_fields.keys()) + + assert headings_fields == { + "family", + "weight", + "style", + "line_height", + "color", + "background_color", + } + + +def test_brand_typography_fields_monospace(): + fields = set(BrandTypographyMonospace.model_fields.keys()) + + assert fields == {"family", "weight", "style", "size"} + + +def test_brand_typography_fields_monospace_inline(): + fields = set(BrandTypographyMonospaceInline.model_fields.keys()) + + assert fields == { + "family", + "weight", + "style", + "size", + "color", + "background_color", + } + + +def test_brand_typography_fields_monospace_block(): + fields = set(BrandTypographyMonospaceBlock.model_fields.keys()) + + assert fields == { + "family", + "weight", + "style", + "size", + "line_height", + "color", + "background_color", + } + + +def test_brand_typography_fields_link(): + fields = set(BrandTypographyLink.model_fields.keys()) + + assert fields == { + "weight", + "decoration", + "color", + "background_color", + } From f5eceb527a8b03a190470aefe0b68d0efa52c121 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 10 Sep 2024 16:59:16 -0400 Subject: [PATCH 043/119] feat(typography): Add Font Bunny foundry --- pkg-py/src/brand_yaml/typography.py | 23 +++++++++++++++++++---- pkg-py/tests/test_typography.py | 21 +++++++++++++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 1a1d7d5d..bfc01c4b 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -8,6 +8,8 @@ ConfigDict, Discriminator, Field, + HttpUrl, + PositiveInt, RootModel, Tag, field_validator, @@ -159,6 +161,8 @@ class BrandTypographyFontGoogle(BaseModel): weight: SingleOrList[BrandTypographyFontWeightAllType] = [400, 700] style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" + version: PositiveInt = 2 + url: HttpUrl = Field("https://fonts.googleapis.com/") @field_validator("weight", mode="before") @classmethod @@ -169,10 +173,20 @@ def validate_weight(cls, value: SingleOrList[Union[int, str]]): return validate_font_weight(value) +class BrandTypographyFontBunny(BrandTypographyFontGoogle): + model_config = ConfigDict(extra="forbid") + + source: Literal["bunny"] = "bunny" + version: PositiveInt = 1 + url: HttpUrl = Field("https://fonts.bunny.net/") + + def brand_typography_font_discriminator( x: dict[str, object] | BrandTypographyFontFile | BrandTypographyFontGoogle, ) -> Literal["google", "file"]: - if isinstance(x, BrandTypographyFontGoogle): + if isinstance(x, BrandTypographyFontBunny): + return "bunny" + elif isinstance(x, BrandTypographyFontGoogle): return "google" elif isinstance(x, BrandTypographyFontFile): return "file" @@ -181,13 +195,13 @@ def brand_typography_font_discriminator( if not isinstance(value, str): pass - elif value == "google": - return "google" + elif value in ("google", "bunny"): + return value elif Path(value).suffix: return "file" raise ValueError( - "Unsupported font source {value!r}, must be a file path or {'google'!r}." + "Unsupported font source {value!r}, must be a file path, 'google', or 'bunny'." ) @@ -289,6 +303,7 @@ class BrandTypography(BrandBase): Annotated[ Union[ Annotated[BrandTypographyFontGoogle, Tag("google")], + Annotated[BrandTypographyFontBunny, Tag("bunny")], Annotated[BrandTypographyFontFile, Tag("file")], ], Discriminator(brand_typography_font_discriminator), diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index be5d7188..232efa20 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -5,7 +5,9 @@ from brand_yaml.typography import ( BrandTypography, BrandTypographyBase, + BrandTypographyFontBunny, BrandTypographyFontFile, + BrandTypographyFontGoogle, BrandTypographyHeadings, BrandTypographyLink, BrandTypographyMonospace, @@ -175,3 +177,22 @@ def test_brand_typography_fields_link(): "color", "background_color", } + + +def test_brand_typography_font_bunny(): + bf = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "bunny", + "family": "Kode Mono", + "weight": [400, 500, 600, 700], + "style": "normal", + } + ] + } + ) + + assert len(bf.fonts) == 1 + assert isinstance(bf.fonts[0], BrandTypographyFontBunny) + assert isinstance(bf.fonts[0], BrandTypographyFontGoogle) From dbbc91d3d5c9bc73eefbd2980a51672d76575d19 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 10 Sep 2024 18:14:21 -0400 Subject: [PATCH 044/119] feat(typography): Add import URL for Google Fonts --- pkg-py/src/brand_yaml/typography.py | 69 ++++++++++++++++++++++++++++- pkg-py/tests/test_typography.py | 46 +++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index bfc01c4b..b8f39b0b 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -1,7 +1,9 @@ from __future__ import annotations +import itertools from pathlib import Path from typing import Annotated, Any, Literal, TypeVar, Union +from urllib.parse import urlencode, urljoin from pydantic import ( BaseModel, @@ -172,6 +174,71 @@ def validate_weight(cls, value: SingleOrList[Union[int, str]]): else: return validate_font_weight(value) + def import_url(self) -> str: + if self.version == 1: + return self._import_url_v1() + return self._import_url_v2() + + def _import_url_v1(self) -> str: + weight = sorted( + self.weight if isinstance(self.weight, list) else [self.weight] + ) + style_str = sorted( + self.style if isinstance(self.style, list) else [self.style] + ) + style_map = {"normal": "", "italic": "i"} + ital: list[str] = sorted([style_map[s] for s in style_str]) + + values = [] + if len(weight) > 0 and len(ital) > 0: + values = [f"{w}{i}" for w, i in itertools.product(weight, ital)] + elif len(weight) > 0: + values = [str(w) for w in weight] + elif len(ital) > 0: + values = ["regular" if i == "" else "italic" for i in ital] + + family_values = "" if len(values) == 0 else f":{','.join(values)}" + params = urlencode( + { + "family": self.family + family_values, + "display": self.display, + } + ) + + return urljoin(str(self.url), f"css?{params}") + + def _import_url_v2(self) -> str: + weight = sorted( + self.weight if isinstance(self.weight, list) else [self.weight] + ) + style_str = sorted( + self.style if isinstance(self.style, list) else [self.style] + ) + style_map = {"normal": 0, "italic": 1} + ital: list[int] = sorted([style_map[s] for s in style_str]) + + values = [] + axis = "" + if len(weight) > 0 and len(ital) > 0: + values = [f"{i},{w}" for i, w in itertools.product(ital, weight)] + axis = "ital,wght" + elif len(weight) > 0: + values = [str(w) for w in weight] + axis = "wght" + elif len(ital) > 0: + values = [str(i) for i in ital] + axis = "ital" + + axis_range = f":{axis}@{';'.join(values)}" + params = urlencode( + { + "family": self.family + axis_range, + "display": self.display, + } + ) + + return urljoin(str(self.url), f"css2?{params}") + class BrandTypographyFontBunny(BrandTypographyFontGoogle): model_config = ConfigDict(extra="forbid") @@ -183,7 +250,7 @@ class BrandTypographyFontBunny(BrandTypographyFontGoogle): def brand_typography_font_discriminator( x: dict[str, object] | BrandTypographyFontFile | BrandTypographyFontGoogle, -) -> Literal["google", "file"]: +) -> Literal["google", "bunny", "file"]: if isinstance(x, BrandTypographyFontBunny): return "bunny" elif isinstance(x, BrandTypographyFontGoogle): diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 232efa20..aebe7cb4 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -1,5 +1,7 @@ from __future__ import annotations +from urllib.parse import unquote + import pytest from brand_yaml.typography import ( @@ -196,3 +198,47 @@ def test_brand_typography_font_bunny(): assert len(bf.fonts) == 1 assert isinstance(bf.fonts[0], BrandTypographyFontBunny) assert isinstance(bf.fonts[0], BrandTypographyFontGoogle) + + +def test_brand_typography_font_google_import_url(): + bg = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "google", + "family": "Open Sans", + "weight": [700, 400], + "style": ["italic", "normal"], + } + ] + } + ) + + assert len(bg.fonts) == 1 + assert isinstance(bg.fonts[0], BrandTypographyFontGoogle) + assert ( + unquote(bg.fonts[0].import_url()) + == "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=auto" + ) + + +def test_brand_typography_font_bunny_import_url(): + bg = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "bunny", + "family": "Open Sans", + "weight": [700, 400], + "style": ["italic", "normal"], + } + ] + } + ) + + assert len(bg.fonts) == 1 + assert isinstance(bg.fonts[0], BrandTypographyFontBunny) + assert ( + unquote(bg.fonts[0].import_url()) + == "https://fonts.bunny.net/css?family=Open+Sans:400,400i,700,700i&display=auto" + ) From f2905e370ea3485c828e7b18773f0304ac8fec14 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 10 Sep 2024 18:17:06 -0400 Subject: [PATCH 045/119] feat: create abstract class for google fonts API --- pkg-py/src/brand_yaml/typography.py | 13 ++++++++----- pkg-py/tests/test_typography.py | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index b8f39b0b..d78eda51 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -155,10 +155,7 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: return fmt -class BrandTypographyFontGoogle(BaseModel): - model_config = ConfigDict(extra="forbid") - - source: Literal["google"] = "google" +class BrandTypographyGoogleFontsApi(BaseModel): family: str weight: SingleOrList[BrandTypographyFontWeightAllType] = [400, 700] style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] @@ -240,7 +237,13 @@ def _import_url_v2(self) -> str: return urljoin(str(self.url), f"css2?{params}") -class BrandTypographyFontBunny(BrandTypographyFontGoogle): +class BrandTypographyFontGoogle(BrandTypographyGoogleFontsApi): + model_config = ConfigDict(extra="forbid") + + source: Literal["google"] = "google" + + +class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): model_config = ConfigDict(extra="forbid") source: Literal["bunny"] = "bunny" diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index aebe7cb4..83fd0b7f 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -10,6 +10,7 @@ BrandTypographyFontBunny, BrandTypographyFontFile, BrandTypographyFontGoogle, + BrandTypographyGoogleFontsApi, BrandTypographyHeadings, BrandTypographyLink, BrandTypographyMonospace, @@ -197,7 +198,7 @@ def test_brand_typography_font_bunny(): assert len(bf.fonts) == 1 assert isinstance(bf.fonts[0], BrandTypographyFontBunny) - assert isinstance(bf.fonts[0], BrandTypographyFontGoogle) + assert isinstance(bf.fonts[0], BrandTypographyGoogleFontsApi) def test_brand_typography_font_google_import_url(): From 1c8a7447863a0b461ec7cafb0ed789a338df47f8 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 11 Sep 2024 08:49:45 -0400 Subject: [PATCH 046/119] feat(typography): Monospace fields inherit from same parent class Also forward `monospace` to `monospace_inline` for consistency so the presence of `monospace` implies the presence of `monospace_inline` and `monospace_block`. --- examples/brand-typography-simple.yml | 12 ++++++++++ pkg-py/src/brand_yaml/typography.py | 36 ++++++++++++++++++---------- pkg-py/tests/test_typography.py | 23 ++++++++++++++++++ 3 files changed, 59 insertions(+), 12 deletions(-) create mode 100644 examples/brand-typography-simple.yml diff --git a/examples/brand-typography-simple.yml b/examples/brand-typography-simple.yml new file mode 100644 index 00000000..befb9cc5 --- /dev/null +++ b/examples/brand-typography-simple.yml @@ -0,0 +1,12 @@ +typography: + base: + family: Open Sans + line-height: 1.25 + size: 1rem + headings: + family: Roboto Slab + color: primary + weight: 600 + monospace: + family: Fira Code + size: 0.9em \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index d78eda51..1aef9adb 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -338,18 +338,14 @@ class BrandTypographyMonospace( class BrandTypographyMonospaceInline( - BrandBase, - BrandTypographyOptionsGenericText, - BrandTypographyOptionsSize, + BrandTypographyMonospace, BrandTypographyOptionsColor, ): model_config = ConfigDict(extra="forbid") class BrandTypographyMonospaceBlock( - BrandBase, - BrandTypographyOptionsGenericText, - BrandTypographyOptionsSize, + BrandTypographyMonospace, BrandTypographyOptionsBlockText, BrandTypographyOptionsColor, ): @@ -398,18 +394,34 @@ def forward_monospace_values(self): `monospace-inline` and `monospace-block` both inherit `family`, `style`, `weight` and `size` from `monospace`. """ - - def use_fallback(obj: BaseModel | None, parent: BaseModel | None): - if parent is None or obj is None: + if self.monospace is None: + return self + + monospace_defaults = { + k: v + for k, v in self.monospace.model_dump().items() + if v is not None + } + + def use_fallback(key: str): + obj = getattr(self, key) + + if obj is None: + new_type = ( + BrandTypographyMonospaceInline + if key == "monospace_inline" + else BrandTypographyMonospaceBlock + ) + setattr(self, key, new_type.model_validate(monospace_defaults)) return for field in ("family", "style", "weight", "size"): - fallback = getattr(parent, field) + fallback = monospace_defaults.get(field) if fallback is None: continue if getattr(obj, field) is None: setattr(obj, field, fallback) - use_fallback(self.monospace_inline, self.monospace) - use_fallback(self.monospace_block, self.monospace) + use_fallback("monospace_inline") + use_fallback("monospace_block") return self diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 83fd0b7f..d0f28532 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -3,7 +3,9 @@ from urllib.parse import unquote import pytest +from utils import path_examples +from brand_yaml import read_brand_yaml from brand_yaml.typography import ( BrandTypography, BrandTypographyBase, @@ -243,3 +245,24 @@ def test_brand_typography_font_bunny_import_url(): unquote(bg.fonts[0].import_url()) == "https://fonts.bunny.net/css?family=Open+Sans:400,400i,700,700i&display=auto" ) + + +def test_brand_typography_ex_simple(): + brand = read_brand_yaml(path_examples("brand-typography-simple.yml")) + + assert isinstance(brand.typography, BrandTypography) + assert isinstance(brand.typography.base, BrandTypographyBase) + assert isinstance(brand.typography.headings, BrandTypographyHeadings) + assert isinstance(brand.typography.monospace, BrandTypographyMonospace) + assert isinstance( + brand.typography.monospace_inline, BrandTypographyMonospace + ) + assert isinstance( + brand.typography.monospace_block, BrandTypographyMonospace + ) + assert isinstance( + brand.typography.monospace_inline, BrandTypographyMonospaceInline + ) + assert isinstance( + brand.typography.monospace_block, BrandTypographyMonospaceBlock + ) From c806b843e17b327c7171b8fa1032d180856aeea3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 11 Sep 2024 09:42:34 -0400 Subject: [PATCH 047/119] tests(typography): Add snapshot tests --- examples/brand-typography-fonts.yml | 37 ++++++++++++ examples/brand-typography-simple.yml | 4 +- pkg-py/pyproject.toml | 1 + .../test_brand_typography_ex_fonts.json | 56 +++++++++++++++++++ .../test_brand_typography_ex_simple.json | 29 ++++++++++ pkg-py/tests/test_typography.py | 50 ++++++++++++++++- pkg-py/tests/utils/__init__.py | 13 +++++ pkg-py/uv.lock | 14 +++++ 8 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 examples/brand-typography-fonts.yml create mode 100644 pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json create mode 100644 pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json diff --git a/examples/brand-typography-fonts.yml b/examples/brand-typography-fonts.yml new file mode 100644 index 00000000..bbdc2387 --- /dev/null +++ b/examples/brand-typography-fonts.yml @@ -0,0 +1,37 @@ +meta: + name: examples/brand-typography-fonts.yml +typography: + fonts: + # Local files + # TODO: validate file paths, currently accepts arbitrary strings + - source: Open-Sans.ttf + family: Open Sans + - source: Open-Sans-Bold.ttf + family: Open Sans + weight: bold + - source: Open-Sans-Italic.ttf + family: Open Sans + style: italic + + # Online Fonts + - source: google + family: Roboto Slab + weight: semi-bold + style: normal + display: block + - source: bunny + family: Fira Code + + base: + family: Open Sans + line-height: 1.25 + size: 1rem + + headings: + family: Roboto Slab + color: primary + weight: 600 + + monospace: + family: Fira Code + size: 0.9em diff --git a/examples/brand-typography-simple.yml b/examples/brand-typography-simple.yml index befb9cc5..5fb735a8 100644 --- a/examples/brand-typography-simple.yml +++ b/examples/brand-typography-simple.yml @@ -1,3 +1,5 @@ +meta: + name: examples/brand-typography-simple.yml typography: base: family: Open Sans @@ -6,7 +8,7 @@ typography: headings: family: Roboto Slab color: primary - weight: 600 + weight: semi-bold monospace: family: Fira Code size: 0.9em \ No newline at end of file diff --git a/pkg-py/pyproject.toml b/pkg-py/pyproject.toml index bad50dea..1e07ed2e 100644 --- a/pkg-py/pyproject.toml +++ b/pkg-py/pyproject.toml @@ -20,6 +20,7 @@ quarto = [ dev = [ "pyright>=1.1.379", "pytest>=8.3.2", + "syrupy>=4.7.1", ] [build-system] diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json new file mode 100644 index 00000000..8697d87f --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json @@ -0,0 +1,56 @@ +{ + "meta": { + "name": "examples/brand-typography-fonts.yml" + }, + "typography": { + "base": { + "family": "Open Sans", + "line-height": 1.25, + "size": "1rem" + }, + "fonts": [ + { + "family": "Open Sans", + "source": "Open-Sans.ttf" + }, + { + "family": "Open Sans", + "source": "Open-Sans-Bold.ttf", + "weight": "bold" + }, + { + "family": "Open Sans", + "source": "Open-Sans-Italic.ttf", + "style": "italic" + }, + { + "display": "block", + "family": "Roboto Slab", + "source": "google", + "style": "normal", + "weight": 600 + }, + { + "family": "Fira Code", + "source": "bunny" + } + ], + "headings": { + "color": "primary", + "family": "Roboto Slab", + "weight": 600 + }, + "monospace": { + "family": "Fira Code", + "size": "0.9em" + }, + "monospace-block": { + "family": "Fira Code", + "size": "0.9em" + }, + "monospace-inline": { + "family": "Fira Code", + "size": "0.9em" + } + } +} diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json new file mode 100644 index 00000000..c1859d82 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json @@ -0,0 +1,29 @@ +{ + "meta": { + "name": "examples/brand-typography-simple.yml" + }, + "typography": { + "base": { + "family": "Open Sans", + "line-height": 1.25, + "size": "1rem" + }, + "headings": { + "color": "primary", + "family": "Roboto Slab", + "weight": 600 + }, + "monospace": { + "family": "Fira Code", + "size": "0.9em" + }, + "monospace-block": { + "family": "Fira Code", + "size": "0.9em" + }, + "monospace-inline": { + "family": "Fira Code", + "size": "0.9em" + } + } +} diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index d0f28532..570b6108 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -3,7 +3,8 @@ from urllib.parse import unquote import pytest -from utils import path_examples +from syrupy.extensions.json import JSONSnapshotExtension +from utils import path_examples, pydantic_data_from_json from brand_yaml import read_brand_yaml from brand_yaml.typography import ( @@ -21,6 +22,11 @@ ) +@pytest.fixture +def snapshot_json(snapshot): + return snapshot.use_extension(JSONSnapshotExtension) + + @pytest.mark.parametrize( "source, fmt", [ @@ -247,10 +253,12 @@ def test_brand_typography_font_bunny_import_url(): ) -def test_brand_typography_ex_simple(): +def test_brand_typography_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-typography-simple.yml")) assert isinstance(brand.typography, BrandTypography) + assert brand.typography.fonts == [] + assert brand.typography.link is None assert isinstance(brand.typography.base, BrandTypographyBase) assert isinstance(brand.typography.headings, BrandTypographyHeadings) assert isinstance(brand.typography.monospace, BrandTypographyMonospace) @@ -266,3 +274,41 @@ def test_brand_typography_ex_simple(): assert isinstance( brand.typography.monospace_block, BrandTypographyMonospaceBlock ) + + assert snapshot_json == pydantic_data_from_json(brand) + + +def test_brand_typography_ex_fonts(snapshot_json): + brand = read_brand_yaml(path_examples("brand-typography-fonts.yml")) + + assert isinstance(brand.typography, BrandTypography) + assert len(brand.typography.fonts) == 5 + + for font in brand.typography.fonts[:3]: + assert isinstance(font, BrandTypographyFontFile) + assert font.source.startswith("Open-Sans") + assert font.source.endswith(".ttf") + assert font.format == "truetype" + + assert [f.weight for f in brand.typography.fonts[:3]] == [ + "normal", + "bold", + "normal", + ] + + assert [f.style for f in brand.typography.fonts[:3]] == [ + "normal", + "normal", + "italic", + ] + + assert isinstance(brand.typography.fonts[3], BrandTypographyFontGoogle) + assert brand.typography.fonts[3].family == "Roboto Slab" + assert brand.typography.fonts[3].weight == 600 + assert brand.typography.fonts[3].style == "normal" + assert brand.typography.fonts[3].display == "block" + + assert isinstance(brand.typography.fonts[4], BrandTypographyFontBunny) + assert brand.typography.fonts[4].family == "Fira Code" + + assert snapshot_json == pydantic_data_from_json(brand) diff --git a/pkg-py/tests/utils/__init__.py b/pkg-py/tests/utils/__init__.py index 7ccffcb8..13f9f519 100644 --- a/pkg-py/tests/utils/__init__.py +++ b/pkg-py/tests/utils/__init__.py @@ -1,6 +1,19 @@ +import json from pathlib import Path +from pydantic import BaseModel + def path_examples(*args) -> Path: repo_root = Path(__file__).parent.parent.parent.parent return repo_root / "examples" / Path(*args) + + +def pydantic_data_from_json(model: BaseModel) -> dict: + data = model.model_dump_json( + by_alias=True, + indent=2, + exclude_unset=True, + exclude_none=True, + ) + return json.loads(data) diff --git a/pkg-py/uv.lock b/pkg-py/uv.lock index 69518305..c44021a7 100644 --- a/pkg-py/uv.lock +++ b/pkg-py/uv.lock @@ -58,6 +58,7 @@ dependencies = [ dev = [ { name = "pyright" }, { name = "pytest" }, + { name = "syrupy" }, ] quarto = [ { name = "nbclient" }, @@ -76,6 +77,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.2" }, { name = "pyyaml", marker = "extra == 'quarto'", specifier = ">=6.0.2" }, { name = "ruamel-yaml", specifier = ">=0.18.6" }, + { name = "syrupy", marker = "extra == 'dev'", specifier = ">=4.7.1" }, ] [[package]] @@ -810,6 +812,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] +[[package]] +name = "syrupy" +version = "4.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/ac/105c151335bf71ddf7f3c77118438cad77d4cf092559a6b429bca1bb436b/syrupy-4.7.1.tar.gz", hash = "sha256:f9d4485f3f27d0e5df6ed299cac6fa32eb40a441915d988e82be5a4bdda335c8", size = 49117 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/af9adb7a0e4420dcf249653f589cd27152fa6daab5cfd84e6d665dcd7df5/syrupy-4.7.1-py3-none-any.whl", hash = "sha256:be002267a512a4bedddfae2e026c93df1ea928ae10baadc09640516923376d41", size = 49135 }, +] + [[package]] name = "tomli" version = "2.0.1" From c43b99dd0faf21c7327125d7ccf47a3cf2c0b6f2 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 11 Sep 2024 10:33:43 -0400 Subject: [PATCH 048/119] chore: move pyproject.toml to the root of the repository --- pkg-py/pyproject.toml => pyproject.toml | 16 +- pkg-py/uv.lock => uv.lock | 328 +++++++++++------------- 2 files changed, 168 insertions(+), 176 deletions(-) rename pkg-py/pyproject.toml => pyproject.toml (80%) rename pkg-py/uv.lock => uv.lock (69%) diff --git a/pkg-py/pyproject.toml b/pyproject.toml similarity index 80% rename from pkg-py/pyproject.toml rename to pyproject.toml index 1e07ed2e..79ffe52c 100644 --- a/pkg-py/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "brand-yaml" version = "0.1.0" description = "Read brand yaml files, a unified way to store brand information." -readme = "README.md" +readme = "pkg-py/README.md" requires-python = ">=3.9" dependencies = [ "ruamel-yaml>=0.18.6", @@ -27,10 +27,22 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.version] +path = "pkg-py/src/brand_yaml/__init__.py" +pattern = "BUILD = 'b(?P[^']+)'" + +[tool.hatch.build] +skip-excluded-dirs = true + +[tool.hatch.build.targets.wheel] +packages = ["pkg-py/src/brand_yaml"] + [tool.pyright] -exclude = ["_dev", ".venv", "src/brand_yaml/_brand.py"] +include = ["pkg-py"] +exclude = ["pkg-py/_dev", "pkg-py/.venv", "pkg-py/src/brand_yaml/_brand.py"] [tool.ruff] +src = ["pkg-py"] exclude = [ ".bzr", ".direnv", diff --git a/pkg-py/uv.lock b/uv.lock similarity index 69% rename from pkg-py/uv.lock rename to uv.lock index c44021a7..bb095319 100644 --- a/pkg-py/uv.lock +++ b/uv.lock @@ -1,28 +1,8 @@ version = 1 requires-python = ">=3.9" resolution-markers = [ - "python_full_version < '3.10' and platform_system != 'Emscripten'", - "python_full_version < '3.10' and platform_system == 'Emscripten'", - "python_full_version < '3.11' and platform_system != 'Emscripten'", - "python_full_version < '3.11' and platform_system == 'Emscripten'", - "python_full_version < '3.12' and platform_system != 'Emscripten'", - "python_full_version < '3.12' and platform_system == 'Emscripten'", - "python_full_version < '3.13' and platform_system != 'Emscripten'", - "python_full_version < '3.13' and platform_system == 'Emscripten'", - "python_full_version < '3.10' and platform_system != 'Emscripten'", - "python_full_version < '3.10' and platform_system == 'Emscripten'", - "python_full_version == '3.10.*' and platform_system != 'Emscripten'", - "python_full_version == '3.10.*' and platform_system == 'Emscripten'", - "python_full_version == '3.11.*' and platform_system != 'Emscripten'", - "python_full_version == '3.11.*' and platform_system == 'Emscripten'", - "python_full_version == '3.12.*' and platform_system != 'Emscripten'", - "python_full_version == '3.12.*' and platform_system == 'Emscripten'", - "python_full_version < '3.13' and platform_system != 'Emscripten'", - "python_full_version < '3.13' and platform_system == 'Emscripten'", - "python_full_version >= '3.13' and python_full_version < '4.0' and platform_system != 'Emscripten'", - "python_full_version >= '3.13' and python_full_version < '4.0' and platform_system == 'Emscripten'", - "python_full_version >= '4.0' and platform_system != 'Emscripten'", - "python_full_version >= '4.0' and platform_system == 'Emscripten'", + "python_full_version < '3.13'", + "python_full_version >= '3.13'", ] [[package]] @@ -82,71 +62,71 @@ requires-dist = [ [[package]] name = "cffi" -version = "1.17.0" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pycparser" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/bf/82c351342972702867359cfeba5693927efe0a8dd568165490144f554b18/cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76", size = 516073 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/00/2a/9071bf1e20bf9f695643b6c3e0f838f340b95ee29de0d1bb7968772409be/cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb", size = 181841 }, - { url = "https://files.pythonhosted.org/packages/4b/42/60116f10466d692b64aef32ac40fd79b11344ab6ef889ff8e3d047f2fcb2/cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a", size = 178242 }, - { url = "https://files.pythonhosted.org/packages/26/8e/a53f844454595c6e9215e56cda123db3427f8592f2c7b5ef1be782f620d6/cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42", size = 425676 }, - { url = "https://files.pythonhosted.org/packages/60/ac/6402563fb40b64c7ccbea87836d9c9498b374629af3449f3d8ff34df187d/cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d", size = 447842 }, - { url = "https://files.pythonhosted.org/packages/b2/e7/e2ffdb8de59f48f17b196813e9c717fbed2364e39b10bdb3836504e89486/cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2", size = 455224 }, - { url = "https://files.pythonhosted.org/packages/59/55/3e8968e92fe35c1c368959a070a1276c10cae29cdad0fd0daa36c69e237e/cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab", size = 436341 }, - { url = "https://files.pythonhosted.org/packages/7f/df/700aaf009dfbfa04acb1ed487586c03c788c6a312f0361ad5f298c5f5a7d/cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b", size = 445861 }, - { url = "https://files.pythonhosted.org/packages/5a/70/637f070aae533ea11ab77708a820f3935c0edb4fbcef9393b788e6f426a5/cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206", size = 460982 }, - { url = "https://files.pythonhosted.org/packages/f7/1a/7d4740fa1ccc4fcc888963fc3165d69ef1a2c8d42c8911c946703ff5d4a5/cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa", size = 438434 }, - { url = "https://files.pythonhosted.org/packages/d0/d9/c48cc38aaf6f53a8b5d2dbf6fe788410fcbab33b15a69c56c01d2b08f6a2/cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f", size = 461219 }, - { url = "https://files.pythonhosted.org/packages/26/ec/b6a7f660a7f27bd2bb53fe99a2ccafa279088395ec8639b25b8950985b2d/cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc", size = 171406 }, - { url = "https://files.pythonhosted.org/packages/08/42/8c00824787e6f5ec55194f5cd30c4ba4b9d9d5bb0d4d0007b1bb948d4ad4/cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2", size = 180809 }, - { url = "https://files.pythonhosted.org/packages/53/cc/9298fb6235522e00e47d78d6aa7f395332ef4e5f6fe124f9a03aa60600f7/cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720", size = 181912 }, - { url = "https://files.pythonhosted.org/packages/e7/79/dc5334fbe60635d0846c56597a8d2af078a543ff22bc48d36551a0de62c2/cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9", size = 178297 }, - { url = "https://files.pythonhosted.org/packages/39/d7/ef1b6b16b51ccbabaced90ff0d821c6c23567fc4b2e4a445aea25d3ceb92/cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb", size = 444909 }, - { url = "https://files.pythonhosted.org/packages/29/b8/6e3c61885537d985c78ef7dd779b68109ba256263d74a2f615c40f44548d/cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424", size = 468854 }, - { url = "https://files.pythonhosted.org/packages/0b/49/adad1228e19b931e523c2731e6984717d5f9e33a2f9971794ab42815b29b/cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d", size = 476890 }, - { url = "https://files.pythonhosted.org/packages/76/54/c00f075c3e7fd14d9011713bcdb5b4f105ad044c5ad948db7b1a0a7e4e78/cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8", size = 459374 }, - { url = "https://files.pythonhosted.org/packages/f3/b9/f163bb3fa4fbc636ee1f2a6a4598c096cdef279823ddfaa5734e556dd206/cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6", size = 466891 }, - { url = "https://files.pythonhosted.org/packages/31/52/72bbc95f6d06ff2e88a6fa13786be4043e542cb24748e1351aba864cb0a7/cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91", size = 477658 }, - { url = "https://files.pythonhosted.org/packages/67/20/d694811457eeae0c7663fa1a7ca201ce495533b646c1180d4ac25684c69c/cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8", size = 453890 }, - { url = "https://files.pythonhosted.org/packages/dc/79/40cbf5739eb4f694833db5a27ce7f63e30a9b25b4a836c4f25fb7272aacc/cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb", size = 478254 }, - { url = "https://files.pythonhosted.org/packages/e9/eb/2c384c385cca5cae67ca10ac4ef685277680b8c552b99aedecf4ea23ff7e/cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9", size = 171285 }, - { url = "https://files.pythonhosted.org/packages/ca/42/74cb1e0f1b79cb64672f3cb46245b506239c1297a20c0d9c3aeb3929cb0c/cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0", size = 180842 }, - { url = "https://files.pythonhosted.org/packages/1a/1f/7862231350cc959a3138889d2c8d33da7042b22e923457dfd4cd487d772a/cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc", size = 182826 }, - { url = "https://files.pythonhosted.org/packages/8b/8c/26119bf8b79e05a1c39812064e1ee7981e1f8a5372205ba5698ea4dd958d/cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59", size = 178494 }, - { url = "https://files.pythonhosted.org/packages/61/94/4882c47d3ad396d91f0eda6ef16d45be3d752a332663b7361933039ed66a/cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb", size = 454459 }, - { url = "https://files.pythonhosted.org/packages/0f/7c/a6beb119ad515058c5ee1829742d96b25b2b9204ff920746f6e13bf574eb/cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195", size = 478502 }, - { url = "https://files.pythonhosted.org/packages/61/8a/2575cd01a90e1eca96a30aec4b1ac101a6fae06c49d490ac2704fa9bc8ba/cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e", size = 485381 }, - { url = "https://files.pythonhosted.org/packages/cd/66/85899f5a9f152db49646e0c77427173e1b77a1046de0191ab3b0b9a5e6e3/cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828", size = 470907 }, - { url = "https://files.pythonhosted.org/packages/00/13/150924609bf377140abe6e934ce0a57f3fc48f1fd956ec1f578ce97a4624/cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150", size = 479074 }, - { url = "https://files.pythonhosted.org/packages/17/fd/7d73d7110155c036303b0a6462c56250e9bc2f4119d7591d27417329b4d1/cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a", size = 484225 }, - { url = "https://files.pythonhosted.org/packages/fc/83/8353e5c9b01bb46332dac3dfb18e6c597a04ceb085c19c814c2f78a8c0d0/cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885", size = 488388 }, - { url = "https://files.pythonhosted.org/packages/73/0c/f9d5ca9a095b1fc88ef77d1f8b85d11151c374144e4606da33874e17b65b/cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492", size = 172096 }, - { url = "https://files.pythonhosted.org/packages/72/21/8c5d285fe20a6e31d29325f1287bb0e55f7d93630a5a44cafdafb5922495/cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2", size = 181478 }, - { url = "https://files.pythonhosted.org/packages/17/8f/581f2f3c3464d5f7cf87c2f7a5ba9acc6976253e02d73804240964243ec2/cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118", size = 182638 }, - { url = "https://files.pythonhosted.org/packages/8d/1c/c9afa66684b7039f48018eb11b229b659dfb32b7a16b88251bac106dd1ff/cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7", size = 178453 }, - { url = "https://files.pythonhosted.org/packages/cc/b6/1a134d479d3a5a1ff2fabbee551d1d3f1dd70f453e081b5f70d604aae4c0/cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377", size = 454441 }, - { url = "https://files.pythonhosted.org/packages/b1/b4/e1569475d63aad8042b0935dbf62ae2a54d1e9142424e2b0e924d2d4a529/cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb", size = 478543 }, - { url = "https://files.pythonhosted.org/packages/d2/40/a9ad03fbd64309dec5bb70bc803a9a6772602de0ee164d7b9a6ca5a89249/cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555", size = 485463 }, - { url = "https://files.pythonhosted.org/packages/a6/1a/f10be60e006dd9242a24bcc2b1cd55c34c578380100f742d8c610f7a5d26/cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204", size = 470854 }, - { url = "https://files.pythonhosted.org/packages/cc/b3/c035ed21aa3d39432bd749fe331ee90e4bc83ea2dbed1f71c4bc26c41084/cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f", size = 479096 }, - { url = "https://files.pythonhosted.org/packages/00/cb/6f7edde01131de9382c89430b8e253b8c8754d66b63a62059663ceafeab2/cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0", size = 484013 }, - { url = "https://files.pythonhosted.org/packages/b9/83/8e4e8c211ea940210d293e951bf06b1bfb90f2eeee590e9778e99b4a8676/cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4", size = 488119 }, - { url = "https://files.pythonhosted.org/packages/5e/52/3f7cfbc4f444cb4f73ff17b28690d12436dde665f67d68f1e1687908ab6c/cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a", size = 172122 }, - { url = "https://files.pythonhosted.org/packages/94/19/cf5baa07ee0f0e55eab7382459fbddaba0fdb0ba45973dd92556ae0d02db/cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7", size = 181504 }, - { url = "https://files.pythonhosted.org/packages/96/22/7866bf5450d6a5b8cf4123abde25b2126fce03ac4efc1244a44367b01c65/cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2", size = 181868 }, - { url = "https://files.pythonhosted.org/packages/0c/03/934cd50132c1637a52ab41c093ff89b93086181f6cdc40d43185083818c1/cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759", size = 178261 }, - { url = "https://files.pythonhosted.org/packages/4a/1e/06c7bc7ed387e42f0ecdef2477a5b291455c2158bb7a565848ef96bba113/cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4", size = 424564 }, - { url = "https://files.pythonhosted.org/packages/b7/9b/43f26a558d192bb0691051153add44404af0adf6e3e35d5ce83340d41a92/cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82", size = 446854 }, - { url = "https://files.pythonhosted.org/packages/b5/5c/7777c4b0fc212caf180b20ec51da3d9fa00910d40f042004d33679f39ec7/cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf", size = 454217 }, - { url = "https://files.pythonhosted.org/packages/8f/90/a40b9821755bd3dfd2dd9a341b660cd57dfa2fc3bb9d8c4499477fa27ae3/cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058", size = 435285 }, - { url = "https://files.pythonhosted.org/packages/e1/d3/36e54b85f670400ff0440ab743fa0de66bdd89b8f54b7d2370708cdcb03f/cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932", size = 444868 }, - { url = "https://files.pythonhosted.org/packages/15/aa/62f87ceb24b03e42061050b1139864347fd73291d2b70b3daefd0c4fdaa8/cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693", size = 460136 }, - { url = "https://files.pythonhosted.org/packages/d4/b6/7abfb922035cc03d2a6c05b6e90f55d60bfea26ef97a2d10357b3f0bdbf3/cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3", size = 437565 }, - { url = "https://files.pythonhosted.org/packages/83/a8/306c52a4625eef30a6d7828c0c7ecaf9a11e1fc83efe506d6fcf980b68c7/cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4", size = 460284 }, - { url = "https://files.pythonhosted.org/packages/a8/05/4daca3a5d2af2af95828b35e65221d4f8afb6155c9d80a1ebda7a11348ab/cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb", size = 171382 }, - { url = "https://files.pythonhosted.org/packages/89/2d/ec3ae32daf8713681ded997aa2e6d68306c11a41627fb351201111ea0d24/cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29", size = 180820 }, +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, + { url = "https://files.pythonhosted.org/packages/b9/ea/8bb50596b8ffbc49ddd7a1ad305035daa770202a6b782fc164647c2673ad/cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16", size = 182220 }, + { url = "https://files.pythonhosted.org/packages/ae/11/e77c8cd24f58285a82c23af484cf5b124a376b32644e445960d1a4654c3a/cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36", size = 178605 }, + { url = "https://files.pythonhosted.org/packages/ed/65/25a8dc32c53bf5b7b6c2686b42ae2ad58743f7ff644844af7cdb29b49361/cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8", size = 424910 }, + { url = "https://files.pythonhosted.org/packages/42/7a/9d086fab7c66bd7c4d0f27c57a1b6b068ced810afc498cc8c49e0088661c/cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576", size = 447200 }, + { url = "https://files.pythonhosted.org/packages/da/63/1785ced118ce92a993b0ec9e0d0ac8dc3e5dbfbcaa81135be56c69cabbb6/cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87", size = 454565 }, + { url = "https://files.pythonhosted.org/packages/74/06/90b8a44abf3556599cdec107f7290277ae8901a58f75e6fe8f970cd72418/cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0", size = 435635 }, + { url = "https://files.pythonhosted.org/packages/bd/62/a1f468e5708a70b1d86ead5bab5520861d9c7eacce4a885ded9faa7729c3/cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3", size = 445218 }, + { url = "https://files.pythonhosted.org/packages/5b/95/b34462f3ccb09c2594aa782d90a90b045de4ff1f70148ee79c69d37a0a5a/cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595", size = 460486 }, + { url = "https://files.pythonhosted.org/packages/fc/fc/a1e4bebd8d680febd29cf6c8a40067182b64f00c7d105f8f26b5bc54317b/cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a", size = 437911 }, + { url = "https://files.pythonhosted.org/packages/e6/c3/21cab7a6154b6a5ea330ae80de386e7665254835b9e98ecc1340b3a7de9a/cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e", size = 460632 }, + { url = "https://files.pythonhosted.org/packages/cb/b5/fd9f8b5a84010ca169ee49f4e4ad6f8c05f4e3545b72ee041dbbcb159882/cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7", size = 171820 }, + { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 }, ] [[package]] @@ -314,11 +294,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.2.2" +version = "4.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 } +sdist = { url = "https://files.pythonhosted.org/packages/75/a0/d7cab8409cdc7d39b037c85ac46d92434fb6595432e069251b38e5c8dd0e/platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", size = 21276 } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 }, + { url = "https://files.pythonhosted.org/packages/da/8b/d497999c4017b80678017ddce745cf675489c110681ad3c84a55eddfd3e7/platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617", size = 18417 }, ] [[package]] @@ -341,120 +321,120 @@ wheels = [ [[package]] name = "pydantic" -version = "2.8.2" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/99/d0a5dca411e0a017762258013ba9905cd6e7baa9a3fd1fe8b6529472902e/pydantic-2.8.2.tar.gz", hash = "sha256:6f62c13d067b0755ad1c21a34bdd06c0c12625a22b0fc09c6b149816604f7c2a", size = 739834 } +sdist = { url = "https://files.pythonhosted.org/packages/14/15/3d989541b9c8128b96d532cfd2dd10131ddcc75a807330c00feb3d42a5bd/pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2", size = 768511 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/fa/b7f815b8c9ad021c07f88875b601222ef5e70619391ade4a49234d12d278/pydantic-2.8.2-py3-none-any.whl", hash = "sha256:73ee9fddd406dc318b885c7a2eab8a6472b68b8fb5ba8150949fc3db939f23c8", size = 423875 }, + { url = "https://files.pythonhosted.org/packages/e4/28/fff23284071bc1ba419635c7e86561c8b9b8cf62a5bcb459b92d7625fd38/pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612", size = 434363 }, ] [[package]] name = "pydantic-core" -version = "2.20.1" +version = "2.23.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/e3/0d5ad91211dba310f7ded335f4dad871172b9cc9ce204f5a56d76ccd6247/pydantic_core-2.20.1.tar.gz", hash = "sha256:26ca695eeee5f9f1aeeb211ffc12f10bcb6f71e2989988fda61dabd65db878d4", size = 388371 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/9d/f30f080f745682e762512f3eef1f6e392c7d74a102e6e96de8a013a5db84/pydantic_core-2.20.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3acae97ffd19bf091c72df4d726d552c473f3576409b2a7ca36b2f535ffff4a3", size = 1837257 }, - { url = "https://files.pythonhosted.org/packages/f2/89/77e7aebdd4a235497ac1e07f0a99e9f40e47f6e0f6783fe30500df08fc42/pydantic_core-2.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41f4c96227a67a013e7de5ff8f20fb496ce573893b7f4f2707d065907bffdbd6", size = 1776715 }, - { url = "https://files.pythonhosted.org/packages/18/50/5a4e9120b395108c2a0441a425356c0d26a655d7c617288bec1c28b854ac/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f239eb799a2081495ea659d8d4a43a8f42cd1fe9ff2e7e436295c38a10c286a", size = 1789023 }, - { url = "https://files.pythonhosted.org/packages/c7/e5/f19e13ba86b968d024b56aa53f40b24828652ac026e5addd0ae49eeada02/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53e431da3fc53360db73eedf6f7124d1076e1b4ee4276b36fb25514544ceb4a3", size = 1775598 }, - { url = "https://files.pythonhosted.org/packages/c9/c7/f3c29bed28bd022c783baba5bf9946c4f694cb837a687e62f453c81eb5c6/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1f62b2413c3a0e846c3b838b2ecd6c7a19ec6793b2a522745b0869e37ab5bc1", size = 1977691 }, - { url = "https://files.pythonhosted.org/packages/41/3e/f62c2a05c554fff34570f6788617e9670c83ed7bc07d62a55cccd1bc0be6/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d41e6daee2813ecceea8eda38062d69e280b39df793f5a942fa515b8ed67953", size = 2693214 }, - { url = "https://files.pythonhosted.org/packages/ae/49/8a6fe79d35e2f3bea566d8ea0e4e6f436d4f749d7838c8e8c4c5148ae706/pydantic_core-2.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d482efec8b7dc6bfaedc0f166b2ce349df0011f5d2f1f25537ced4cfc34fd98", size = 2061047 }, - { url = "https://files.pythonhosted.org/packages/51/c6/585355c7c8561e11197dbf6333c57dd32f9f62165d48589b57ced2373d97/pydantic_core-2.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e93e1a4b4b33daed65d781a57a522ff153dcf748dee70b40c7258c5861e1768a", size = 1895106 }, - { url = "https://files.pythonhosted.org/packages/ce/23/829f6b87de0775919e82f8addef8b487ace1c77bb4cb754b217f7b1301b6/pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e7c4ea22b6739b162c9ecaaa41d718dfad48a244909fe7ef4b54c0b530effc5a", size = 1968506 }, - { url = "https://files.pythonhosted.org/packages/ca/2f/f8ca8f0c40b3ee0a4d8730a51851adb14c5eda986ec09f8d754b2fba784e/pydantic_core-2.20.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4f2790949cf385d985a31984907fecb3896999329103df4e4983a4a41e13e840", size = 2110217 }, - { url = "https://files.pythonhosted.org/packages/bb/a0/1876656c7b17eb69cc683452cce6bb890dd722222a71b3de57ddb512f561/pydantic_core-2.20.1-cp310-none-win32.whl", hash = "sha256:5e999ba8dd90e93d57410c5e67ebb67ffcaadcea0ad973240fdfd3a135506250", size = 1709669 }, - { url = "https://files.pythonhosted.org/packages/be/4a/576524eefa9b301c088c4818dc50ff1c51a88fe29efd87ab75748ae15fd7/pydantic_core-2.20.1-cp310-none-win_amd64.whl", hash = "sha256:512ecfbefef6dac7bc5eaaf46177b2de58cdf7acac8793fe033b24ece0b9566c", size = 1902386 }, - { url = "https://files.pythonhosted.org/packages/61/db/f6a724db226d990a329910727cfac43539ff6969edc217286dd05cda3ef6/pydantic_core-2.20.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d2a8fa9d6d6f891f3deec72f5cc668e6f66b188ab14bb1ab52422fe8e644f312", size = 1834507 }, - { url = "https://files.pythonhosted.org/packages/9b/83/6f2bfe75209d557ae1c3550c1252684fc1827b8b12fbed84c3b4439e135d/pydantic_core-2.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:175873691124f3d0da55aeea1d90660a6ea7a3cfea137c38afa0a5ffabe37b88", size = 1773527 }, - { url = "https://files.pythonhosted.org/packages/93/ef/513ea76d7ca81f2354bb9c8d7839fc1157673e652613f7e1aff17d8ce05d/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37eee5b638f0e0dcd18d21f59b679686bbd18917b87db0193ae36f9c23c355fc", size = 1787879 }, - { url = "https://files.pythonhosted.org/packages/31/0a/ac294caecf235f0cc651de6232f1642bb793af448d1cfc541b0dc1fd72b8/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25e9185e2d06c16ee438ed39bf62935ec436474a6ac4f9358524220f1b236e43", size = 1774694 }, - { url = "https://files.pythonhosted.org/packages/46/a4/08f12b5512f095963550a7cb49ae010e3f8f3f22b45e508c2cb4d7744fce/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:150906b40ff188a3260cbee25380e7494ee85048584998c1e66df0c7a11c17a6", size = 1976369 }, - { url = "https://files.pythonhosted.org/packages/15/59/b2495be4410462aedb399071c71884042a2c6443319cbf62d00b4a7ed7a5/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ad4aeb3e9a97286573c03df758fc7627aecdd02f1da04516a86dc159bf70121", size = 2691250 }, - { url = "https://files.pythonhosted.org/packages/3c/ae/fc99ce1ba791c9e9d1dee04ce80eef1dae5b25b27e3fc8e19f4e3f1348bf/pydantic_core-2.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3f3ed29cd9f978c604708511a1f9c2fdcb6c38b9aae36a51905b8811ee5cbf1", size = 2061462 }, - { url = "https://files.pythonhosted.org/packages/44/bb/eb07cbe47cfd638603ce3cb8c220f1a054b821e666509e535f27ba07ca5f/pydantic_core-2.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0dae11d8f5ded51699c74d9548dcc5938e0804cc8298ec0aa0da95c21fff57b", size = 1893923 }, - { url = "https://files.pythonhosted.org/packages/ce/ef/5a52400553b8faa0e7f11fd7a2ba11e8d2feb50b540f9e7973c49b97eac0/pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:faa6b09ee09433b87992fb5a2859efd1c264ddc37280d2dd5db502126d0e7f27", size = 1966779 }, - { url = "https://files.pythonhosted.org/packages/4c/5b/fb37fe341344d9651f5c5f579639cd97d50a457dc53901aa8f7e9f28beb9/pydantic_core-2.20.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9dc1b507c12eb0481d071f3c1808f0529ad41dc415d0ca11f7ebfc666e66a18b", size = 2109044 }, - { url = "https://files.pythonhosted.org/packages/70/1a/6f7278802dbc66716661618807ab0dfa4fc32b09d1235923bbbe8b3a5757/pydantic_core-2.20.1-cp311-none-win32.whl", hash = "sha256:fa2fddcb7107e0d1808086ca306dcade7df60a13a6c347a7acf1ec139aa6789a", size = 1708265 }, - { url = "https://files.pythonhosted.org/packages/35/7f/58758c42c61b0bdd585158586fecea295523d49933cb33664ea888162daf/pydantic_core-2.20.1-cp311-none-win_amd64.whl", hash = "sha256:40a783fb7ee353c50bd3853e626f15677ea527ae556429453685ae32280c19c2", size = 1901750 }, - { url = "https://files.pythonhosted.org/packages/6f/47/ef0d60ae23c41aced42921728650460dc831a0adf604bfa66b76028cb4d0/pydantic_core-2.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:595ba5be69b35777474fa07f80fc260ea71255656191adb22a8c53aba4479231", size = 1839225 }, - { url = "https://files.pythonhosted.org/packages/6a/23/430f2878c9cd977a61bb39f71751d9310ec55cee36b3d5bf1752c6341fd0/pydantic_core-2.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4f55095ad087474999ee28d3398bae183a66be4823f753cd7d67dd0153427c9", size = 1768604 }, - { url = "https://files.pythonhosted.org/packages/9e/2b/ec4e7225dee79e0dc80ccc3c35ab33cc2c4bbb8a1a7ecf060e5e453651ec/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9aa05d09ecf4c75157197f27cdc9cfaeb7c5f15021c6373932bf3e124af029f", size = 1789767 }, - { url = "https://files.pythonhosted.org/packages/64/b0/38b24a1fa6d2f96af3148362e10737ec073768cd44d3ec21dca3be40a519/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e97fdf088d4b31ff4ba35db26d9cc472ac7ef4a2ff2badeabf8d727b3377fc52", size = 1772061 }, - { url = "https://files.pythonhosted.org/packages/5e/da/bb73274c42cb60decfa61e9eb0c9029da78b3b9af0a9de0309dbc8ff87b6/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc633a9fe1eb87e250b5c57d389cf28998e4292336926b0b6cdaee353f89a237", size = 1974573 }, - { url = "https://files.pythonhosted.org/packages/c8/65/41693110fb3552556180460daffdb8bbeefb87fc026fd9aa4b849374015c/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d573faf8eb7e6b1cbbcb4f5b247c60ca8be39fe2c674495df0eb4318303137fe", size = 2625596 }, - { url = "https://files.pythonhosted.org/packages/09/b3/a5a54b47cccd1ab661ed5775235c5e06924753c2d4817737c5667bfa19a8/pydantic_core-2.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26dc97754b57d2fd00ac2b24dfa341abffc380b823211994c4efac7f13b9e90e", size = 2099064 }, - { url = "https://files.pythonhosted.org/packages/52/fa/443a7a6ea54beaba45ff3a59f3d3e6e3004b7460bcfb0be77bcf98719d3b/pydantic_core-2.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:33499e85e739a4b60c9dac710c20a08dc73cb3240c9a0e22325e671b27b70d24", size = 1900345 }, - { url = "https://files.pythonhosted.org/packages/8e/e6/9aca9ffae60f9cdf0183069de3e271889b628d0fb175913fcb3db5618fb1/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bebb4d6715c814597f85297c332297c6ce81e29436125ca59d1159b07f423eb1", size = 1968252 }, - { url = "https://files.pythonhosted.org/packages/46/5e/6c716810ea20a6419188992973a73c2fb4eb99cd382368d0637ddb6d3c99/pydantic_core-2.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:516d9227919612425c8ef1c9b869bbbee249bc91912c8aaffb66116c0b447ebd", size = 2119191 }, - { url = "https://files.pythonhosted.org/packages/06/fc/6123b00a9240fbb9ae0babad7a005d51103d9a5d39c957a986f5cdd0c271/pydantic_core-2.20.1-cp312-none-win32.whl", hash = "sha256:469f29f9093c9d834432034d33f5fe45699e664f12a13bf38c04967ce233d688", size = 1717788 }, - { url = "https://files.pythonhosted.org/packages/d5/36/e61ad5a46607a469e2786f398cd671ebafcd9fb17f09a2359985c7228df5/pydantic_core-2.20.1-cp312-none-win_amd64.whl", hash = "sha256:035ede2e16da7281041f0e626459bcae33ed998cca6a0a007a5ebb73414ac72d", size = 1898188 }, - { url = "https://files.pythonhosted.org/packages/49/75/40b0e98b658fdba02a693b3bacb4c875a28bba87796c7b13975976597d8c/pydantic_core-2.20.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0827505a5c87e8aa285dc31e9ec7f4a17c81a813d45f70b1d9164e03a813a686", size = 1838688 }, - { url = "https://files.pythonhosted.org/packages/75/02/d8ba2d4a266591a6a623c68b331b96523d4b62ab82a951794e3ed8907390/pydantic_core-2.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:19c0fa39fa154e7e0b7f82f88ef85faa2a4c23cc65aae2f5aea625e3c13c735a", size = 1768409 }, - { url = "https://files.pythonhosted.org/packages/91/ae/25ecd9bc4ce4993e99a1a3c9ab111c082630c914260e129572fafed4ecc2/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa223cd1e36b642092c326d694d8bf59b71ddddc94cdb752bbbb1c5c91d833b", size = 1789317 }, - { url = "https://files.pythonhosted.org/packages/7a/80/72057580681cdbe55699c367963d9c661b569a1d39338b4f6239faf36cdc/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c336a6d235522a62fef872c6295a42ecb0c4e1d0f1a3e500fe949415761b8a19", size = 1771949 }, - { url = "https://files.pythonhosted.org/packages/a2/be/d9bbabc55b05019013180f141fcaf3b14dbe15ca7da550e95b60c321009a/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7eb6a0587eded33aeefea9f916899d42b1799b7b14b8f8ff2753c0ac1741edac", size = 1974392 }, - { url = "https://files.pythonhosted.org/packages/79/2d/7bcd938c6afb0f40293283f5f09988b61fb0a4f1d180abe7c23a2f665f8e/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:70c8daf4faca8da5a6d655f9af86faf6ec2e1768f4b8b9d0226c02f3d6209703", size = 2625565 }, - { url = "https://files.pythonhosted.org/packages/ac/88/ca758e979457096008a4b16a064509028e3e092a1e85a5ed6c18ced8da88/pydantic_core-2.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fa4c9bf273ca41f940bceb86922a7667cd5bf90e95dbb157cbb8441008482c", size = 2098784 }, - { url = "https://files.pythonhosted.org/packages/eb/de/2fad6d63c3c42e472e985acb12ec45b7f56e42e6f4cd6dfbc5e87ee8678c/pydantic_core-2.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:11b71d67b4725e7e2a9f6e9c0ac1239bbc0c48cce3dc59f98635efc57d6dac83", size = 1900198 }, - { url = "https://files.pythonhosted.org/packages/fe/50/077c7f35b6488dc369a6d22993af3a37901e198630f38ac43391ca730f5b/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:270755f15174fb983890c49881e93f8f1b80f0b5e3a3cc1394a255706cabd203", size = 1968005 }, - { url = "https://files.pythonhosted.org/packages/5d/1f/f378631574ead46d636b9a04a80ff878b9365d4b361b1905ef1667d4182a/pydantic_core-2.20.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c81131869240e3e568916ef4c307f8b99583efaa60a8112ef27a366eefba8ef0", size = 2118920 }, - { url = "https://files.pythonhosted.org/packages/7a/ea/e4943f17df7a3031d709481fe4363d4624ae875a6409aec34c28c9e6cf59/pydantic_core-2.20.1-cp313-none-win32.whl", hash = "sha256:b91ced227c41aa29c672814f50dbb05ec93536abf8f43cd14ec9521ea09afe4e", size = 1717397 }, - { url = "https://files.pythonhosted.org/packages/13/63/b95781763e8d84207025071c0cec16d921c0163c7a9033ae4b9a0e020dc7/pydantic_core-2.20.1-cp313-none-win_amd64.whl", hash = "sha256:65db0f2eefcaad1a3950f498aabb4875c8890438bc80b19362cf633b87a8ab20", size = 1898013 }, - { url = "https://files.pythonhosted.org/packages/17/c3/803028de61ce9a1fe1643f77ff845807c76298bf1995fa216c4ae853c6b9/pydantic_core-2.20.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:b03f7941783b4c4a26051846dea594628b38f6940a2fdc0df00b221aed39314c", size = 1838087 }, - { url = "https://files.pythonhosted.org/packages/77/f7/25f1fba7ea1ae052e20b234e4c66d54b129e5b3f4d1e6c0da6534dbf57c3/pydantic_core-2.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1eedfeb6089ed3fad42e81a67755846ad4dcc14d73698c120a82e4ccf0f1f9f6", size = 1722218 }, - { url = "https://files.pythonhosted.org/packages/57/53/fe2e1ae3795b7a69f81913584174f8ed36446b56df734565260830a3632b/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:635fee4e041ab9c479e31edda27fcf966ea9614fff1317e280d99eb3e5ab6fe2", size = 1788970 }, - { url = "https://files.pythonhosted.org/packages/13/80/d9c698486f8fb64b0945e0844c95eef3bcff920941eda30d556deadadbdf/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:77bf3ac639c1ff567ae3b47f8d4cc3dc20f9966a2a6dd2311dcc055d3d04fb8a", size = 1775836 }, - { url = "https://files.pythonhosted.org/packages/0f/0c/ab6df185529c0ce1a6d916f9d159de389cc7de44eaa9362efc76495fb821/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ed1b0132f24beeec5a78b67d9388656d03e6a7c837394f99257e2d55b461611", size = 1978099 }, - { url = "https://files.pythonhosted.org/packages/0e/9f/3094afeb286c60ec08088d938b661a561f3d23cd2e88a90a92ab0ecfce4f/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6514f963b023aeee506678a1cf821fe31159b925c4b76fe2afa94cc70b3222b", size = 2693403 }, - { url = "https://files.pythonhosted.org/packages/9b/f1/a006955715be98093d092aa025f604c7c00721e83fe04bf467c49f31a685/pydantic_core-2.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10d4204d8ca33146e761c79f83cc861df20e7ae9f6487ca290a97702daf56006", size = 2061754 }, - { url = "https://files.pythonhosted.org/packages/32/f6/cd2e7bd0a52e2a72841f60c32e62b269995c34bdb13e4d1e799be834338a/pydantic_core-2.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d036c7187b9422ae5b262badb87a20a49eb6c5238b2004e96d4da1231badef1", size = 1895490 }, - { url = "https://files.pythonhosted.org/packages/ac/22/34ce27579901fcca525f8adce7747760407cf284c4f0fec6d4542265b451/pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9ebfef07dbe1d93efb94b4700f2d278494e9162565a54f124c404a5656d7ff09", size = 1968678 }, - { url = "https://files.pythonhosted.org/packages/2f/3a/80df9b0b5ea5e5b8939285c600dc9ce4a185317f5fb065a37e77a20cbdb3/pydantic_core-2.20.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6b9d9bb600328a1ce523ab4f454859e9d439150abb0906c5a1983c146580ebab", size = 2110873 }, - { url = "https://files.pythonhosted.org/packages/ec/26/998c9b8dadcdeafbc833964ef5975cd0c7516b0157575b26300d078ae239/pydantic_core-2.20.1-cp39-none-win32.whl", hash = "sha256:784c1214cb6dd1e3b15dd8b91b9a53852aed16671cc3fbe4786f4f1db07089e2", size = 1709309 }, - { url = "https://files.pythonhosted.org/packages/ed/36/67aeb15996618882c5cfe85dbeffefe09e2806cd86bdd37bca40753e82a1/pydantic_core-2.20.1-cp39-none-win_amd64.whl", hash = "sha256:d2fe69c5434391727efa54b47a1e7986bb0186e72a41b203df8f5b0a19a4f669", size = 1905736 }, - { url = "https://files.pythonhosted.org/packages/73/73/0c7265903f66cce39ed7ca939684fba344210cefc91ccc999cfd5b113fd3/pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a45f84b09ac9c3d35dfcf6a27fd0634d30d183205230a0ebe8373a0e8cfa0906", size = 1828190 }, - { url = "https://files.pythonhosted.org/packages/27/55/60b8b0e58b49ee3ed36a18562dd7c6bc06a551c390e387af5872a238f2ec/pydantic_core-2.20.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d02a72df14dfdbaf228424573a07af10637bd490f0901cee872c4f434a735b94", size = 1715252 }, - { url = "https://files.pythonhosted.org/packages/28/3d/d66314bad6bb777a36559195a007b31e916bd9e2c198f7bb8f4ccdceb4fa/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b27e6af28f07e2f195552b37d7d66b150adbaa39a6d327766ffd695799780f", size = 1782641 }, - { url = "https://files.pythonhosted.org/packages/9e/f5/f178f4354d0d6c1431a8f9ede71f3c4269ac4dc55d314fdb7555814276dc/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:084659fac3c83fd674596612aeff6041a18402f1e1bc19ca39e417d554468482", size = 1928788 }, - { url = "https://files.pythonhosted.org/packages/9c/51/1f5e27bb194df79e30b593b608c66e881ed481241e2b9ed5bdf86d165480/pydantic_core-2.20.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:242b8feb3c493ab78be289c034a1f659e8826e2233786e36f2893a950a719bb6", size = 1886116 }, - { url = "https://files.pythonhosted.org/packages/ac/76/450d9258c58dc7c70b9e3aadf6bebe23ddd99e459c365e2adbde80e238da/pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:38cf1c40a921d05c5edc61a785c0ddb4bed67827069f535d794ce6bcded919fc", size = 1960125 }, - { url = "https://files.pythonhosted.org/packages/dd/9e/0309a7a4bea51771729515e413b3987be0789837de99087f7415e0db1f9b/pydantic_core-2.20.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e0bbdd76ce9aa5d4209d65f2b27fc6e5ef1312ae6c5333c26db3f5ade53a1e99", size = 2100407 }, - { url = "https://files.pythonhosted.org/packages/af/93/06d44e08277b3b818b75bd5f25e879d7693e4b7dd3505fde89916fcc9ca2/pydantic_core-2.20.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:254ec27fdb5b1ee60684f91683be95e5133c994cc54e86a0b0963afa25c8f8a6", size = 1914966 }, - { url = "https://files.pythonhosted.org/packages/ff/d0/639b12bc7c81ebcbbd5f946327e8970089b23fa5b11d7abb56495cbdc0de/pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:407653af5617f0757261ae249d3fba09504d7a71ab36ac057c938572d1bc9331", size = 1829108 }, - { url = "https://files.pythonhosted.org/packages/f1/80/3b9d7fb8b4f8d36e24373334740c0b88d9ded08342543a72e9247b4fa410/pydantic_core-2.20.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c693e916709c2465b02ca0ad7b387c4f8423d1db7b4649c551f27a529181c5ad", size = 1716448 }, - { url = "https://files.pythonhosted.org/packages/2f/c6/f80ea0fac8c241c066245fe918cdc9d105985a1a8726aced9478548c9e37/pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b5ff4911aea936a47d9376fd3ab17e970cc543d1b68921886e7f64bd28308d1", size = 1783620 }, - { url = "https://files.pythonhosted.org/packages/d5/3e/9af260156f79347ed3e64149836d69bfe1e0c5efadec6116a879fc31c9ec/pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f55a886d74f1808763976ac4efd29b7ed15c69f4d838bbd74d9d09cf6fa86", size = 1929929 }, - { url = "https://files.pythonhosted.org/packages/d1/fe/8c3e928e10a97eb8e85b18a53ed3288d039cf0fd7b0fe8d3258f14e8500a/pydantic_core-2.20.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:964faa8a861d2664f0c7ab0c181af0bea66098b1919439815ca8803ef136fc4e", size = 1886708 }, - { url = "https://files.pythonhosted.org/packages/31/26/b670bd58f1de902c099ff623fe62b9820448a20d70437e7698a57b922d3a/pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:4dd484681c15e6b9a977c785a345d3e378d72678fd5f1f3c0509608da24f2ac0", size = 1960709 }, - { url = "https://files.pythonhosted.org/packages/de/ee/322cad098a0cffc81e985ac2a298d3f29a1da25efe7dc1fb5cd2615c5b04/pydantic_core-2.20.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f6d6cff3538391e8486a431569b77921adfcdef14eb18fbf19b7c0a5294d4e6a", size = 2101218 }, - { url = "https://files.pythonhosted.org/packages/07/8b/30233f741e16b35499fa2fad2f4a69eb127eec6c850a1b14af26e7b08b73/pydantic_core-2.20.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a6d511cc297ff0883bc3708b465ff82d7560193169a8b93260f74ecb0a5e08a7", size = 1915399 }, +sdist = { url = "https://files.pythonhosted.org/packages/5c/cc/07bec3fb337ff80eacd6028745bd858b9642f61ee58cfdbfb64451c1def0/pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690", size = 402277 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/fb/fc7077473d843fd70bd1e09177c3225be95621881765d6f7d123036fb9c7/pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6", size = 1845897 }, + { url = "https://files.pythonhosted.org/packages/92/8c/c6f1a0f72328c5687acc0847baf806c4cb31c1a9321de70c3cbcbb37cece/pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5", size = 1777037 }, + { url = "https://files.pythonhosted.org/packages/bd/fc/89e2a998218230ed8c38f0ba11d8f73947df90ac59a1e9f2fb4e1ba318a5/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b", size = 1801481 }, + { url = "https://files.pythonhosted.org/packages/d7/f3/81a5f69ea1359633876ea2283728d0afe2ed62e028d91d747dcdfabc594e/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700", size = 1807280 }, + { url = "https://files.pythonhosted.org/packages/7a/91/b20f5646d7ef7c2629744b49e6fb86f839aa676b1aa11fb3998371ac5860/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01", size = 2003100 }, + { url = "https://files.pythonhosted.org/packages/89/71/59172c61f2ecd4b33276774512ef31912944429fabaa0f4483151f788a35/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed", size = 2662832 }, + { url = "https://files.pythonhosted.org/packages/80/d1/c6f8e23987dc166976996a910876596635d71e529335b846880d856589fd/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec", size = 2057218 }, + { url = "https://files.pythonhosted.org/packages/ae/f3/f4381383b65cf16392aead51643fd5fb3feeb69972226d276ce5c6cfb948/pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba", size = 1923455 }, + { url = "https://files.pythonhosted.org/packages/a1/8d/d845077d39e55763bdb99d64ef86f8961827f8896b6e58ce08ce6b255bde/pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee", size = 1966890 }, + { url = "https://files.pythonhosted.org/packages/53/f8/56355d7b1cf84df63f93b1a455ebb53fd9588edbb63a44fd4d801444a060/pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe", size = 2112163 }, + { url = "https://files.pythonhosted.org/packages/06/32/a0a7a3a318b4ae98a0e6b9e18db31fadbd3cfc46b31191e4ed4ca658e2d4/pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b", size = 1717086 }, + { url = "https://files.pythonhosted.org/packages/e3/31/38aebe234508fc30c80b4825661d3c1ef0d51b1c40a12e50855b108acd35/pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83", size = 1918933 }, + { url = "https://files.pythonhosted.org/packages/4a/60/ef8eaad365c1d94962d158633f66313e051f7b90cead647e65a96993da22/pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27", size = 1843251 }, + { url = "https://files.pythonhosted.org/packages/57/f4/20aa352e03379a3b5d6c2fb951a979f70718138ea747e3f756d63dda69da/pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45", size = 1776367 }, + { url = "https://files.pythonhosted.org/packages/f1/b9/e5482ac4ea2d128925759d905fb05a08ca98e67ed1d8ab7401861997c6c8/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611", size = 1800135 }, + { url = "https://files.pythonhosted.org/packages/78/9f/387353f6b6b2ed023f973cffa4e2384bb2e52d15acf5680bc70c50f6c48f/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61", size = 1805896 }, + { url = "https://files.pythonhosted.org/packages/4f/70/9a153f19394e2ef749f586273ebcdb3de97e2fa97e175b957a8e5a2a77f9/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5", size = 2001492 }, + { url = "https://files.pythonhosted.org/packages/a5/1c/79d976846fcdcae0c657922d0f476ca287fa694e69ac1fc9d397b831e1cc/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0", size = 2659827 }, + { url = "https://files.pythonhosted.org/packages/fd/89/cdd76ae363cabae23a4b70df50d603c81c517415ff9d5d65e72e35251cf6/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8", size = 2055160 }, + { url = "https://files.pythonhosted.org/packages/1a/82/7d62c3dd4e2e101a81ac3fa138d986bfbad9727a6275fc2b4a5efb98bdbd/pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8", size = 1922282 }, + { url = "https://files.pythonhosted.org/packages/85/e6/ef09f395c974d08674464dd3d49066612fe7cc0466ef8ce9427cadf13e5b/pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48", size = 1965827 }, + { url = "https://files.pythonhosted.org/packages/a4/5e/e589474af850c77c3180b101b54bc98bf812ad09728ba2cff4989acc9734/pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5", size = 2110810 }, + { url = "https://files.pythonhosted.org/packages/e0/ff/626007d5b7ac811f9bcac6d8af3a574ccee4505c1f015d25806101842f0c/pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1", size = 1715479 }, + { url = "https://files.pythonhosted.org/packages/4f/ff/6dc33f3b71e34ef633e35d6476d245bf303fc3eaf18a00f39bb54f78faf3/pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa", size = 1918281 }, + { url = "https://files.pythonhosted.org/packages/8f/35/6d81bc4aa7d06e716f39e2bffb0eabcbcebaf7bab94c2f8278e277ded0ea/pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305", size = 1845250 }, + { url = "https://files.pythonhosted.org/packages/18/42/0821cd46f76406e0fe57df7a89d6af8fddb22cce755bcc2db077773c7d1a/pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb", size = 1769993 }, + { url = "https://files.pythonhosted.org/packages/e5/55/b969088e48bd8ea588548a7194d425de74370b17b385cee4d28f5a79013d/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa", size = 1791250 }, + { url = "https://files.pythonhosted.org/packages/43/c1/1d460d09c012ac76b68b2a1fd426ad624724f93b40e24a9a993763f12c61/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162", size = 1802530 }, + { url = "https://files.pythonhosted.org/packages/70/8e/fd3c9eda00fbdadca726f17a0f863ecd871a65b3a381b77277ae386d3bcd/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801", size = 1997848 }, + { url = "https://files.pythonhosted.org/packages/f0/67/13fa22d7b09395e83721edc31bae2bd5c5e2c36a09d470c18f5d1de46958/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb", size = 2662790 }, + { url = "https://files.pythonhosted.org/packages/fa/1b/1d689c53d15ab67cb0df1c3a2b1df873b50409581e93e4848289dce57e2f/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326", size = 2074114 }, + { url = "https://files.pythonhosted.org/packages/3d/d9/b565048609db77760b9a0900f6e0a3b2f33be47cd3c4a433f49653a0d2b5/pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c", size = 1918153 }, + { url = "https://files.pythonhosted.org/packages/41/94/8ee55c51333ed8df3a6f1e73c6530c724a9a37d326e114c9e3b24faacff9/pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c", size = 1969019 }, + { url = "https://files.pythonhosted.org/packages/f7/49/0233bae5778a5526cef000447a93e8d462f4f13e2214c13c5b23d379cb25/pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab", size = 2121325 }, + { url = "https://files.pythonhosted.org/packages/42/a1/2f262db2fd6f9c2c9904075a067b1764cc6f71c014be5c6c91d9de52c434/pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c", size = 1725252 }, + { url = "https://files.pythonhosted.org/packages/9a/00/a57937080b49500df790c4853d3e7bc605bd0784e4fcaf1a159456f37ef1/pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b", size = 1920660 }, + { url = "https://files.pythonhosted.org/packages/e1/3c/32958c0a5d1935591b58337037a1695782e61261582d93d5a7f55441f879/pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f", size = 1845068 }, + { url = "https://files.pythonhosted.org/packages/92/a1/7e628e19b78e6ffdb2c92cccbb7eca84bfd3276cee4cafcae8833452f458/pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2", size = 1770095 }, + { url = "https://files.pythonhosted.org/packages/bb/17/d15fd8ce143cd1abb27be924eeff3c5c0fe3b0582f703c5a5273c11e67ce/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791", size = 1790964 }, + { url = "https://files.pythonhosted.org/packages/24/cc/37feff1792f09dc33207fbad3897373229279d1973c211f9562abfdf137d/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423", size = 1802384 }, + { url = "https://files.pythonhosted.org/packages/44/d8/ca9acd7f5f044d9ff6e43d7f35aab4b1d5982b4773761eabe3317fc68e30/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63", size = 1997824 }, + { url = "https://files.pythonhosted.org/packages/35/0f/146269dba21b10d5bf86f9a7a7bbeab4ce1db06f466a1ab5ec3dec68b409/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9", size = 2662907 }, + { url = "https://files.pythonhosted.org/packages/5a/7d/9573f006e39cd1a7b7716d1a264e3f4f353cf0a6042c04c01c6e31666f62/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5", size = 2073953 }, + { url = "https://files.pythonhosted.org/packages/7e/a5/25200aaafd1e97e2ec3c1eb4b357669dd93911f2eba252bc60b6ba884fff/pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855", size = 1917822 }, + { url = "https://files.pythonhosted.org/packages/3e/b4/ac069c58e3cee70c69f03693222cc173fdf740d20d53167bceafc1efc7ca/pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4", size = 1968838 }, + { url = "https://files.pythonhosted.org/packages/d1/3d/9f96bbd6212b4b0a6dc6d037e446208d3420baba2b2b81e544094b18a859/pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d", size = 2121468 }, + { url = "https://files.pythonhosted.org/packages/ac/50/7399d536d6600d69059a87fff89861332c97a7b3471327a3663c7576e707/pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8", size = 1725373 }, + { url = "https://files.pythonhosted.org/packages/24/ba/9ac8744ab636c1161c598cc5e8261379b6b0f1d63c31242bf9d5ed41ed32/pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1", size = 1920594 }, + { url = "https://files.pythonhosted.org/packages/b8/9c/cb69375fd9488869c4c29edf6666050ce5c88baf755926f4121aacd9f01f/pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8", size = 1846402 }, + { url = "https://files.pythonhosted.org/packages/b5/7d/99d47c7084e39465781552f65889f92b1673a31c179753e476385326a3b6/pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e", size = 1730388 }, + { url = "https://files.pythonhosted.org/packages/80/0d/e6be39d563846de02a1a61fa942758e6d2409f5a87bb5853f65abde2470a/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d", size = 1801656 }, + { url = "https://files.pythonhosted.org/packages/3e/4a/6d9e8ad6c95be4af18948d400284382bc7f8b00d795f2222f3f094bc4dcb/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28", size = 1807884 }, + { url = "https://files.pythonhosted.org/packages/a9/09/751832a0938384cf78ce0353d38ef350c9ecbf2ebd5dc7ff0b3b3a0f8bfd/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef", size = 2003488 }, + { url = "https://files.pythonhosted.org/packages/4b/1f/77c720b6ca179f59c44a5698163b38be58e735974db28d761b31462da42e/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c", size = 2664470 }, + { url = "https://files.pythonhosted.org/packages/47/71/5aa475102a31edc15bb0df9a6627de64f62b11be99be49f2a4a0d2a19eea/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a", size = 2057855 }, + { url = "https://files.pythonhosted.org/packages/d2/66/15d6378783e2ede05416194848030b35cf732d84cf6cb8897aa916f628a6/pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd", size = 1923691 }, + { url = "https://files.pythonhosted.org/packages/6e/c5/7172805d806012aaff6547d2c819a98bc318313d36a9b10cd48241d85fb1/pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835", size = 1967678 }, + { url = "https://files.pythonhosted.org/packages/2b/51/6e1f5b06a3e70de9ac4d14d5ddf74564c2831ed403bb86808742c26d4240/pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70", size = 2112758 }, + { url = "https://files.pythonhosted.org/packages/3f/e5/1ee8f68f9425728541edb9df26702f95f8243c9e42f405b2a972c64edb1b/pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7", size = 1716954 }, + { url = "https://files.pythonhosted.org/packages/96/67/663492ab80a625d07ca4abd3178023fa79a9f6fa1df4acc3213bff371e9d/pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958", size = 1921529 }, + { url = "https://files.pythonhosted.org/packages/c0/2d/1f4ec8614225b516366f6c4c49d55ec42ebb93004c0bc9a3e0d21d0ed3c0/pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d", size = 1834597 }, + { url = "https://files.pythonhosted.org/packages/4d/f0/665d4cd60147992b1da0f5a9d1fd7f309c7f12999e3a494c4898165c64ab/pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4", size = 1721339 }, + { url = "https://files.pythonhosted.org/packages/a7/02/7b85ae2c3452e6b9f43b89482dc2a2ba771c31d86d93c2a5a250870b243b/pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211", size = 1794316 }, + { url = "https://files.pythonhosted.org/packages/61/09/f0fde8a9d66f37f3e08e03965a9833d71c4b5fb0287d8f625f88d79dfcd6/pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961", size = 1944713 }, + { url = "https://files.pythonhosted.org/packages/61/2b/0bfe144cac991700dbeaff620fed38b0565352acb342f90374ebf1350084/pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e", size = 1916385 }, + { url = "https://files.pythonhosted.org/packages/02/4f/7d1b8a28e4a1dd96cdde9e220627abd4d3a7860eb79cc682ccf828cf93e4/pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc", size = 1959666 }, + { url = "https://files.pythonhosted.org/packages/5d/9a/b2c520ef627001c68cf23990b2de42ba66eae58a3f56f13375ae9aecb88d/pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4", size = 2103742 }, + { url = "https://files.pythonhosted.org/packages/cd/43/b9a88a4e6454fcad63317e3dade687b68ae7d9f324c868411b1ea70218b3/pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b", size = 1916507 }, + { url = "https://files.pythonhosted.org/packages/e7/52/fd89a422e922174728341b594612e9c727f5c07c55e3e436dc3dd626f52d/pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433", size = 1835707 }, + { url = "https://files.pythonhosted.org/packages/be/14/07f8fa279d8c7b414c7e547f868dd1b9f8e76f248f49fb44c2312be62cb0/pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a", size = 1722073 }, + { url = "https://files.pythonhosted.org/packages/18/02/09c3ec4f9b270fd5af8f142b5547c396a1cb2aba6721b374f77a60e4bae4/pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c", size = 1794805 }, + { url = "https://files.pythonhosted.org/packages/e7/5c/2ab3689816702554ac73ea5c435030be5461180d5b18f252ea7890774227/pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541", size = 1945670 }, + { url = "https://files.pythonhosted.org/packages/12/ef/c16db2dc939e2686b63a1cd19e80fda55fff95b7411cc3a34ca7d7d2463e/pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb", size = 1916745 }, + { url = "https://files.pythonhosted.org/packages/00/58/c55081fdfc1a1c26c4d90555c013bbb6193721147154b5ba3dff16c36b96/pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8", size = 1960193 }, + { url = "https://files.pythonhosted.org/packages/10/0e/664177152393180ca06ed393a3d4b16804d0a98ce9ccb460c1d29950ab77/pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25", size = 2104209 }, + { url = "https://files.pythonhosted.org/packages/88/6a/df8adefd9d1052c72ee98b8c50a5eb042cdb3f2fea1f4f58a16046bdac02/pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab", size = 1917304 }, ] [[package]] name = "pyright" -version = "1.1.379" +version = "1.1.380" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/62/fd6f13c417087b8e941c3d7cbc37fcf5ec62b6ae7031837194c0926cb704/pyright-1.1.379.tar.gz", hash = "sha256:6f426cb6443786fa966b930c23ad1941c8cb9fe672e4589daea8d80bb34193ea", size = 17481 } +sdist = { url = "https://files.pythonhosted.org/packages/fa/bc/3bb71d02125dae6730d64bb32571c0eda5e5d86483b198b7f6dd82c49c6f/pyright-1.1.380.tar.gz", hash = "sha256:e6ceb1a5f7e9f03106e0aa1d6fbb4d97735a5e7ffb59f3de6b2db590baf935b2", size = 17487 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/4b/98bb2d6eb98ca35fa5fe90d787c3181310ed153c996af3a7f158487b9a87/pyright-1.1.379-py3-none-any.whl", hash = "sha256:01954811ac71db8646f50de1577576dc275ffb891a9e7324350e676cf6df323f", size = 18221 }, + { url = "https://files.pythonhosted.org/packages/c9/fc/527c58a3b66d5a16dbb124e1c40cf9f207147f5a36f224fb29eb786a0763/pyright-1.1.380-py3-none-any.whl", hash = "sha256:a6404392053d8848bacc7aebcbd9d318bb46baf1a1a000359305481920f43879", size = 18219 }, ] [[package]] name = "pytest" -version = "8.3.2" +version = "8.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -464,9 +444,9 @@ dependencies = [ { name = "pluggy" }, { name = "tomli", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/8c/9862305bdcd6020bc7b45b1b5e7397a6caf1a33d3025b9a003b39075ffb2/pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce", size = 1439314 } +sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/f9/cf155cf32ca7d6fa3601bc4c5dd19086af4b320b706919d48a4c79081cf9/pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5", size = 341802 }, + { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, ] [[package]] From 0ae7a035c56f360ce6afa3064deaaefb8579f9d5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 11 Sep 2024 10:49:14 -0400 Subject: [PATCH 049/119] chore: use standard hatch versioning for now --- pyproject.toml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 79ffe52c..6ddd9ab3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "brand-yaml" -version = "0.1.0" +version = "0.0.1" description = "Read brand yaml files, a unified way to store brand information." readme = "pkg-py/README.md" requires-python = ">=3.9" @@ -27,10 +27,6 @@ dev = [ requires = ["hatchling"] build-backend = "hatchling.build" -[tool.hatch.version] -path = "pkg-py/src/brand_yaml/__init__.py" -pattern = "BUILD = 'b(?P[^']+)'" - [tool.hatch.build] skip-excluded-dirs = true From 41fd12153b319f1903863975889f9c2cceb2f2f5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 11 Sep 2024 10:50:54 -0400 Subject: [PATCH 050/119] chore: add ipykernel to optional quarto deps --- pyproject.toml | 1 + uv.lock | 259 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 259 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6ddd9ab3..dd5c1933 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ quarto = [ "pyyaml>=6.0.2", "nbformat>=5.10.4", "nbclient>=0.10.0", + "ipykernel>=6.29.5", ] dev = [ "pyright>=1.1.379", diff --git a/uv.lock b/uv.lock index bb095319..f1cffb06 100644 --- a/uv.lock +++ b/uv.lock @@ -14,6 +14,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] +[[package]] +name = "appnope" +version = "0.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, +] + +[[package]] +name = "asttokens" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 }, +] + [[package]] name = "attrs" version = "24.2.0" @@ -25,7 +46,7 @@ wheels = [ [[package]] name = "brand-yaml" -version = "0.1.0" +version = "0.0.1" source = { editable = "." } dependencies = [ { name = "eval-type-backport" }, @@ -41,6 +62,7 @@ dev = [ { name = "syrupy" }, ] quarto = [ + { name = "ipykernel" }, { name = "nbclient" }, { name = "nbformat" }, { name = "pyyaml" }, @@ -49,6 +71,7 @@ quarto = [ [package.metadata] requires-dist = [ { name = "eval-type-backport", specifier = ">=0.2.0" }, + { name = "ipykernel", marker = "extra == 'quarto'", specifier = ">=6.29.5" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "nbclient", marker = "extra == 'quarto'", specifier = ">=0.10.0" }, { name = "nbformat", marker = "extra == 'quarto'", specifier = ">=5.10.4" }, @@ -138,6 +161,52 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] +[[package]] +name = "comm" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/a8/fb783cb0abe2b5fded9f55e5703015cdf1c9c85b3669087c538dd15a6a86/comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e", size = 6210 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, +] + +[[package]] +name = "debugpy" +version = "1.8.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/f9/61c325a10ded8dc3ddc3e7cd2ed58c0b15b2ef4bf8b4bf2930ee98ed59ee/debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0", size = 4612118 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/36/0b423f94097cc86555f9a2c8717511863b2a680c9b44b5419d8ac1ff7bf2/debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7", size = 1711184 }, + { url = "https://files.pythonhosted.org/packages/57/0c/c2ec581541923a4d36cee4fd2419c1211c986849fc61097f87aa81fc6ad3/debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a", size = 2997629 }, + { url = "https://files.pythonhosted.org/packages/a8/46/3072c2cd3b20f435968275d316f6aea7ddbb760386324e6578278bc2eb99/debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed", size = 4764678 }, + { url = "https://files.pythonhosted.org/packages/38/25/e738d6f782beba924c0e10dfde2061152f1ea3608dff0e5a5bfb30c311e9/debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e", size = 4788002 }, + { url = "https://files.pythonhosted.org/packages/ad/72/fd138a10dda16775607316d60dd440fcd23e7560e9276da53c597b5917e9/debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a", size = 1786504 }, + { url = "https://files.pythonhosted.org/packages/e2/0e/d0e6af2d7bbf5ace847e4d3bd41f8f9d4a0764fcd8058f07a1c51618cbf2/debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b", size = 2642077 }, + { url = "https://files.pythonhosted.org/packages/f6/55/2a1dc192894ba9b368cdcce15315761a00f2d4cd7de4402179648840e480/debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408", size = 4702081 }, + { url = "https://files.pythonhosted.org/packages/7f/7f/942b23d64f4896e9f8776cf306dfd00feadc950a38d56398610a079b28b1/debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3", size = 4715571 }, + { url = "https://files.pythonhosted.org/packages/9a/82/7d9e1f75fb23c876ab379008c7cf484a1cfa5ed47ccaac8ba37c75e6814e/debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156", size = 1436398 }, + { url = "https://files.pythonhosted.org/packages/fd/b6/ee71d5e73712daf8307a9e85f5e39301abc8b66d13acd04dfff1702e672e/debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb", size = 1437465 }, + { url = "https://files.pythonhosted.org/packages/6c/d8/8e32bf1f2e0142f7e8a2c354338b493e87f2c44e77e233b3a140fb5efa03/debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7", size = 4581313 }, + { url = "https://files.pythonhosted.org/packages/f7/be/2fbaffecb063de228b2b3b6a1750b0b745e5dc645eddd52be8b329933c0b/debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c", size = 4581209 }, + { url = "https://files.pythonhosted.org/packages/13/d9/3cbff9d9927ca0b65f83137a91cf94fc0606c441814e7e74580ff9499d9d/debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c", size = 1721434 }, + { url = "https://files.pythonhosted.org/packages/a4/e6/8cf7f52e1a728b7433c25dba251ae4b475d7c20594c3686f35cf5efa300b/debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406", size = 3064246 }, + { url = "https://files.pythonhosted.org/packages/b6/e1/f78dc2117325534ecddc30f9baf1d04bfd5d0fa8b28db75743b1cbc3bc23/debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34", size = 4774476 }, + { url = "https://files.pythonhosted.org/packages/7a/82/444dc25c1d682b4bedd1d35466f05194d065005f3ed8b4c147514fcbfce4/debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c", size = 4799438 }, + { url = "https://files.pythonhosted.org/packages/02/49/b595c34d7bc690e8d225a6641618a5c111c7e13db5d9e2b756c15ce8f8c6/debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44", size = 4824118 }, +] + +[[package]] +name = "decorator" +version = "5.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/0c/8d907af351aa16b42caae42f9d6aa37b900c67308052d10fdce809f8d952/decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330", size = 35016 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, +] + [[package]] name = "eval-type-backport" version = "0.2.0" @@ -156,6 +225,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/cc/b7e31358aac6ed1ef2bb790a9746ac2c69bcb3c8588b41616914eb106eaf/exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b", size = 16453 }, ] +[[package]] +name = "executing" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/e3/7d45f492c2c4a0e8e0fad57d081a7c8a0286cdd86372b070cca1ec0caa1e/executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab", size = 977485 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/fd/afcd0496feca3276f509df3dbd5dae726fcc756f1a08d9e25abe1733f962/executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf", size = 25805 }, +] + [[package]] name = "fastjsonschema" version = "2.20.0" @@ -186,6 +264,64 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, ] +[[package]] +name = "ipykernel" +version = "6.29.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "appnope", marker = "platform_system == 'Darwin'" }, + { name = "comm" }, + { name = "debugpy" }, + { name = "ipython" }, + { name = "jupyter-client" }, + { name = "jupyter-core" }, + { name = "matplotlib-inline" }, + { name = "nest-asyncio" }, + { name = "packaging" }, + { name = "psutil" }, + { name = "pyzmq" }, + { name = "tornado" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/5c/67594cb0c7055dc50814b21731c22a601101ea3b1b50a9a1b090e11f5d0f/ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215", size = 163367 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5", size = 117173 }, +] + +[[package]] +name = "ipython" +version = "8.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "jedi" }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161 }, +] + +[[package]] +name = "jedi" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/99/99b493cec4bf43176b678de30f81ed003fd6a647a301b9c927280c600f0a/jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd", size = 1227821 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/9f/bc63f0f0737ad7a60800bfd472a4836661adae21f9c2535f3957b1e54ceb/jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0", size = 1569361 }, +] + [[package]] name = "jsonschema" version = "4.23.0" @@ -244,6 +380,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c9/fb/108ecd1fe961941959ad0ee4e12ee7b8b1477247f30b1fdfd83ceaf017f0/jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409", size = 28965 }, ] +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/5b/a36a337438a14116b16480db471ad061c36c3694df7c2084a0da7ba538b7/matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90", size = 8159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/8e/9ad090d3553c280a8060fbf6e24dc1c0c29704ee7d1c372f0c174aa59285/matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca", size = 9899 }, +] + [[package]] name = "nbclient" version = "0.10.0" @@ -274,6 +422,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b", size = 78454 }, ] +[[package]] +name = "nest-asyncio" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/f8/51569ac65d696c8ecbee95938f89d4abf00f47d58d48f6fbabfe8f0baefe/nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", size = 7418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195 }, +] + [[package]] name = "nodeenv" version = "1.9.1" @@ -292,6 +449,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, ] +[[package]] +name = "parso" +version = "0.8.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/94/68e2e17afaa9169cf6412ab0f28623903be73d1b32e208d9e8e541bb086d/parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d", size = 400609 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/ac/dac4a63f978e4dcb3c6d3a78c4d8e0192a113d288502a1216950c41b1027/parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", size = 103650 }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772 }, +] + [[package]] name = "platformdirs" version = "4.3.2" @@ -310,6 +488,53 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, ] +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/6d/0279b119dafc74c1220420028d490c4399b790fc1256998666e3a341879f/prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360", size = 425859 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/23/22750c4b768f09386d1c3cc4337953e8936f48a888fa6dddfb669b2c9088/prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10", size = 386411 }, +] + +[[package]] +name = "psutil" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/18/c7/8c6872f7372eb6a6b2e4708b88419fb46b857f7a2e1892966b851cc79fc9/psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2", size = 508067 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/66/78c9c3020f573c58101dc43a44f6855d01bbbd747e24da2f0c4491200ea3/psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35", size = 249766 }, + { url = "https://files.pythonhosted.org/packages/e1/3f/2403aa9558bea4d3854b0e5e567bc3dd8e9fbc1fc4453c0aa9aafeb75467/psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1", size = 253024 }, + { url = "https://files.pythonhosted.org/packages/0b/37/f8da2fbd29690b3557cca414c1949f92162981920699cd62095a984983bf/psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0", size = 250961 }, + { url = "https://files.pythonhosted.org/packages/35/56/72f86175e81c656a01c4401cd3b1c923f891b31fbcebe98985894176d7c9/psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0", size = 287478 }, + { url = "https://files.pythonhosted.org/packages/19/74/f59e7e0d392bc1070e9a70e2f9190d652487ac115bb16e2eff6b22ad1d24/psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd", size = 290455 }, + { url = "https://files.pythonhosted.org/packages/cd/5f/60038e277ff0a9cc8f0c9ea3d0c5eb6ee1d2470ea3f9389d776432888e47/psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132", size = 292046 }, + { url = "https://files.pythonhosted.org/packages/8b/20/2ff69ad9c35c3df1858ac4e094f20bd2374d33c8643cf41da8fd7cdcb78b/psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d", size = 253560 }, + { url = "https://files.pythonhosted.org/packages/73/44/561092313ae925f3acfaace6f9ddc4f6a9c748704317bad9c8c8f8a36a79/psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3", size = 257399 }, + { url = "https://files.pythonhosted.org/packages/7c/06/63872a64c312a24fb9b4af123ee7007a306617da63ff13bcc1432386ead7/psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0", size = 251988 }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993 }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -420,6 +645,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/88/6a/df8adefd9d1052c72ee98b8c50a5eb042cdb3f2fea1f4f58a16046bdac02/pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab", size = 1917304 }, ] +[[package]] +name = "pygments" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, +] + [[package]] name = "pyright" version = "1.1.380" @@ -792,6 +1026,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 }, ] +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521 }, +] + [[package]] name = "syrupy" version = "4.7.1" @@ -849,6 +1097,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "wcwidth" +version = "0.2.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 }, +] + [[package]] name = "zipp" version = "3.20.1" From 1a94b556cee5c9ab1ad95009bcdf163ec2dd0b13 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 13 Sep 2024 09:54:51 -0400 Subject: [PATCH 051/119] feat(typography): Default to `seq(100, 900, by=100)` for font weights on Google Fonts --- pkg-py/src/brand_yaml/typography.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 1aef9adb..c9692374 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -49,8 +49,26 @@ float, int, Literal["normal", "bold"] ] +BrandTypographyFontWeightRoundIntType = Literal[ + 100, 200, 300, 400, 500, 600, 700, 800, 900 +] + +BrandTypographyFontWeightRoundInt = ( + 100, + 200, + 300, + 400, + 500, + 600, + 700, + 800, + 900, +) + # https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping -BrandTypographyFontWeightMap = { +BrandTypographyFontWeightMap: dict[ + str, BrandTypographyFontWeightRoundIntType +] = { "thin": 100, "extra-light": 200, "ultra-light": 200, @@ -157,7 +175,9 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: class BrandTypographyGoogleFontsApi(BaseModel): family: str - weight: SingleOrList[BrandTypographyFontWeightAllType] = [400, 700] + weight: SingleOrList[BrandTypographyFontWeightSimpleType] = Field( + default=list(BrandTypographyFontWeightRoundInt) + ) style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" version: PositiveInt = 2 @@ -165,7 +185,9 @@ class BrandTypographyGoogleFontsApi(BaseModel): @field_validator("weight", mode="before") @classmethod - def validate_weight(cls, value: SingleOrList[Union[int, str]]): + def validate_weight( + cls, value: SingleOrList[Union[int, str]] + ) -> SingleOrList[BrandTypographyFontWeightSimpleType]: if isinstance(value, list): return [validate_font_weight(x) for x in value] else: From 426a1cf0effb288692b6b1181f805dc3221b35cb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 13 Sep 2024 10:34:51 -0400 Subject: [PATCH 052/119] feat(typography): Restructure `source: file` fonts Matching changes in #3 https://github.com/posit-dev/brand-yml/issues/3#issuecomment-2346836881 --- examples/brand-typography-fonts.yml | 40 +++--- pkg-py/src/brand_yaml/typography.py | 73 ++++++----- .../test_brand_typography_ex_fonts.json | 32 +++-- pkg-py/tests/test_typography.py | 115 ++++++++++-------- 4 files changed, 151 insertions(+), 109 deletions(-) diff --git a/examples/brand-typography-fonts.yml b/examples/brand-typography-fonts.yml index bbdc2387..a06f1cea 100644 --- a/examples/brand-typography-fonts.yml +++ b/examples/brand-typography-fonts.yml @@ -3,24 +3,34 @@ meta: typography: fonts: # Local files - # TODO: validate file paths, currently accepts arbitrary strings - - source: Open-Sans.ttf - family: Open Sans - - source: Open-Sans-Bold.ttf - family: Open Sans - weight: bold - - source: Open-Sans-Italic.ttf - family: Open Sans - style: italic - - # Online Fonts - - source: google - family: Roboto Slab + - family: Open Sans + source: file + files: + - path: Open-Sans-Bold.ttf # TODO: FilePath validation + weight: bold + - path: Open-Sans-Italic.ttf + style: italic + + # Online files + - family: Closed Sans + source: file + files: + - path: https://example.com/Closed-Sans-Bold.woff2 + weight: bold + - path: https://example.com/Closed-Sans-Italic.woff2 + style: italic + + # Online Font Foundries + - family: Roboto Slab + source: google weight: semi-bold style: normal display: block - - source: bunny - family: Fira Code + + - family: Fira Code + source: bunny + # weight: [100, 200, 300, 400, 500, 600, 700, 800, 900] + # style: [normal, italic] base: family: Open Sans diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index c9692374..c9b1d276 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -13,7 +13,6 @@ HttpUrl, PositiveInt, RootModel, - Tag, field_validator, model_validator, ) @@ -135,11 +134,18 @@ def validate_font_weight( return value -class BrandTypographyFontFile(BaseModel): +class BrandTypographyFontFiles(BaseModel): model_config = ConfigDict(extra="forbid") - source: str + source: Literal["file"] = "file" family: str + files: list[BrandTypographyFontFilesPath] + + +class BrandTypographyFontFilesPath(BaseModel): + model_config = ConfigDict(extra="forbid") + + path: str | HttpUrl # TODO: FilePath validation weight: BrandTypographyFontWeightSimpleType = "normal" style: BrandTypographyFontStyleType = "normal" @@ -148,9 +154,9 @@ class BrandTypographyFontFile(BaseModel): def validate_weight(cls, value: int | str): return validate_font_weight(value) - @field_validator("source", mode="after") + @field_validator("path", mode="after") @classmethod - def validate_source(cls, value: str): + def validate_source(cls, value: str) -> str: if not Path(value).suffix: raise BrandUnsupportedFontFileFormat(value) @@ -161,14 +167,15 @@ def validate_source(cls, value: str): @property def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: - source_ext = Path(self.source).suffix + path = str(self.path) + path_ext = Path(path).suffix - if source_ext not in FontFormats: - raise BrandUnsupportedFontFileFormat(self.source) + if path_ext not in FontFormats: + raise BrandUnsupportedFontFileFormat(path) - fmt = FontFormats[source_ext] + fmt = FontFormats[path_ext] if fmt not in ("opentype", "truetype", "woff", "woff2"): - raise BrandUnsupportedFontFileFormat(self.source) + raise BrandUnsupportedFontFileFormat(path) return fmt @@ -273,28 +280,28 @@ class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): url: HttpUrl = Field("https://fonts.bunny.net/") -def brand_typography_font_discriminator( - x: dict[str, object] | BrandTypographyFontFile | BrandTypographyFontGoogle, -) -> Literal["google", "bunny", "file"]: - if isinstance(x, BrandTypographyFontBunny): - return "bunny" - elif isinstance(x, BrandTypographyFontGoogle): - return "google" - elif isinstance(x, BrandTypographyFontFile): - return "file" +# def brand_typography_font_discriminator( +# x: dict[str, object] | BrandTypographyFontFiles | BrandTypographyFontGoogle, +# ) -> Literal["google", "bunny", "file"]: +# if isinstance(x, BrandTypographyFontBunny): +# return "bunny" +# elif isinstance(x, BrandTypographyFontGoogle): +# return "google" +# elif isinstance(x, BrandTypographyFontFiles): +# return "file" - value = x.get("source") +# value = x.get("source") - if not isinstance(value, str): - pass - elif value in ("google", "bunny"): - return value - elif Path(value).suffix: - return "file" +# if not isinstance(value, str): +# pass +# elif value in ("google", "bunny"): +# return value +# elif Path(value).suffix: +# return "file" - raise ValueError( - "Unsupported font source {value!r}, must be a file path, 'google', or 'bunny'." - ) +# raise ValueError( +# "Unsupported font source {value!r}, must be a file path, 'google', or 'bunny'." +# ) class BrandNamedColor(RootModel): @@ -390,11 +397,11 @@ class BrandTypography(BrandBase): fonts: list[ Annotated[ Union[ - Annotated[BrandTypographyFontGoogle, Tag("google")], - Annotated[BrandTypographyFontBunny, Tag("bunny")], - Annotated[BrandTypographyFontFile, Tag("file")], + BrandTypographyFontFiles, + BrandTypographyFontGoogle, + BrandTypographyFontBunny, ], - Discriminator(brand_typography_font_discriminator), + Discriminator("source"), ] ] = Field(default_factory=list) base: BrandTypographyBase | None = None diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json index 8697d87f..fb1f355b 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json @@ -11,17 +11,31 @@ "fonts": [ { "family": "Open Sans", - "source": "Open-Sans.ttf" + "files": [ + { + "path": "Open-Sans-Bold.ttf", + "weight": "bold" + }, + { + "path": "Open-Sans-Italic.ttf", + "style": "italic" + } + ], + "source": "file" }, { - "family": "Open Sans", - "source": "Open-Sans-Bold.ttf", - "weight": "bold" - }, - { - "family": "Open Sans", - "source": "Open-Sans-Italic.ttf", - "style": "italic" + "family": "Closed Sans", + "files": [ + { + "path": "https://example.com/Closed-Sans-Bold.woff2", + "weight": "bold" + }, + { + "path": "https://example.com/Closed-Sans-Italic.woff2", + "style": "italic" + } + ], + "source": "file" }, { "display": "block", diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 570b6108..281e3b33 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -3,15 +3,13 @@ from urllib.parse import unquote import pytest -from syrupy.extensions.json import JSONSnapshotExtension -from utils import path_examples, pydantic_data_from_json - from brand_yaml import read_brand_yaml from brand_yaml.typography import ( BrandTypography, BrandTypographyBase, BrandTypographyFontBunny, - BrandTypographyFontFile, + BrandTypographyFontFiles, + BrandTypographyFontFilesPath, BrandTypographyFontGoogle, BrandTypographyGoogleFontsApi, BrandTypographyHeadings, @@ -20,6 +18,8 @@ BrandTypographyMonospaceBlock, BrandTypographyMonospaceInline, ) +from syrupy.extensions.json import JSONSnapshotExtension +from utils import path_examples, pydantic_data_from_json @pytest.fixture @@ -28,7 +28,7 @@ def snapshot_json(snapshot): @pytest.mark.parametrize( - "source, fmt", + "path, fmt", [ ("my-font.otf", "opentype"), ("my-font.ttf", "truetype"), @@ -36,59 +36,58 @@ def snapshot_json(snapshot): ("my-font.woff2", "woff2"), ], ) -def test_brand_typography_font_file_format(source, fmt): - font = BrandTypographyFontFile(source=source, family="My Font") +def test_brand_typography_font_file_format(path, fmt): + font = BrandTypographyFontFilesPath(path=path) - assert font.source == source + assert font.path == path assert font.format == fmt -def test_brand_typography_font_file_format_ignored(): - # ignores user-provided formats, uses `source` field - BrandTypographyFontFile.model_validate( - {"source": "my-font.otf", "family": "My Font"} - ) - - def test_brand_typography_font_file_weight(): - args = {"source": "my-font.otf", "family": "My Font"} + args = { + "path": "my-font.otf", + } with pytest.raises(ValueError): - BrandTypographyFontFile.model_validate({**args, "weight": "invalid"}) + BrandTypographyFontFilesPath.model_validate( + {**args, "weight": "invalid"} + ) with pytest.raises(ValueError): - BrandTypographyFontFile.model_validate({**args, "weight": 999}) + BrandTypographyFontFilesPath.model_validate({**args, "weight": 999}) with pytest.raises(ValueError): - BrandTypographyFontFile.model_validate({**args, "weight": 150}) + BrandTypographyFontFilesPath.model_validate({**args, "weight": 150}) with pytest.raises(ValueError): - BrandTypographyFontFile.model_validate({**args, "weight": 0}) + BrandTypographyFontFilesPath.model_validate({**args, "weight": 0}) assert ( - BrandTypographyFontFile.model_validate({**args, "weight": 100}).weight + BrandTypographyFontFilesPath.model_validate( + {**args, "weight": 100} + ).weight == 100 ) assert ( - BrandTypographyFontFile.model_validate( + BrandTypographyFontFilesPath.model_validate( {**args, "weight": "thin"} ).weight == 100 ) assert ( - BrandTypographyFontFile.model_validate( + BrandTypographyFontFilesPath.model_validate( {**args, "weight": "semi-bold"} ).weight == 600 ) assert ( - BrandTypographyFontFile.model_validate( + BrandTypographyFontFilesPath.model_validate( {**args, "weight": "bold"} ).weight == "bold" ) assert ( - BrandTypographyFontFile.model_validate( + BrandTypographyFontFilesPath.model_validate( {**args, "weight": "normal"} ).weight == "normal" @@ -282,33 +281,45 @@ def test_brand_typography_ex_fonts(snapshot_json): brand = read_brand_yaml(path_examples("brand-typography-fonts.yml")) assert isinstance(brand.typography, BrandTypography) - assert len(brand.typography.fonts) == 5 - - for font in brand.typography.fonts[:3]: - assert isinstance(font, BrandTypographyFontFile) - assert font.source.startswith("Open-Sans") - assert font.source.endswith(".ttf") + assert len(brand.typography.fonts) == 4 + + # Local Font Files + local_font = brand.typography.fonts[0] + assert isinstance(local_font, BrandTypographyFontFiles) + assert local_font.source == "file" + assert local_font.family == "Open Sans" + for i, font in enumerate(local_font.files): + assert isinstance(font, BrandTypographyFontFilesPath) + assert str(font.path).startswith("Open-Sans") + assert str(font.path).endswith(".ttf") assert font.format == "truetype" - - assert [f.weight for f in brand.typography.fonts[:3]] == [ - "normal", - "bold", - "normal", - ] - - assert [f.style for f in brand.typography.fonts[:3]] == [ - "normal", - "normal", - "italic", - ] - - assert isinstance(brand.typography.fonts[3], BrandTypographyFontGoogle) - assert brand.typography.fonts[3].family == "Roboto Slab" - assert brand.typography.fonts[3].weight == 600 - assert brand.typography.fonts[3].style == "normal" - assert brand.typography.fonts[3].display == "block" - - assert isinstance(brand.typography.fonts[4], BrandTypographyFontBunny) - assert brand.typography.fonts[4].family == "Fira Code" + assert font.weight == ["bold", "normal"][i] + assert font.style == ["normal", "italic"][i] + + # Online Font Files + online_font = brand.typography.fonts[1] + assert isinstance(online_font, BrandTypographyFontFiles) + assert online_font.source == "file" + assert online_font.family == "Closed Sans" + for i, font in enumerate(online_font.files): + assert isinstance(font, BrandTypographyFontFilesPath) + assert str(font.path).startswith("https://") + assert str(font.path).endswith(".woff2") + assert font.format == "woff2" + assert font.weight == ["bold", "normal"][i] + assert font.style == ["normal", "italic"][i] + + # Google Fonts + google_font = brand.typography.fonts[2] + assert isinstance(google_font, BrandTypographyFontGoogle) + assert google_font.family == "Roboto Slab" + assert google_font.weight == 600 + assert google_font.style == "normal" + assert google_font.display == "block" + + # Bunny Fonts + bunny_font = brand.typography.fonts[3] + assert isinstance(bunny_font, BrandTypographyFontBunny) + assert bunny_font.family == "Fira Code" assert snapshot_json == pydantic_data_from_json(brand) From 421929542e2b0eef0f40791e7d88a7efdbf99bc7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 17 Sep 2024 12:20:49 -0400 Subject: [PATCH 053/119] feat(brand.color.palette): Rename from color.with For #18 --- ...irect.yml => brand-color-direct-posit.yml} | 0 ...l.yml => brand-color-palette-internal.yml} | 2 +- ...with.yml => brand-color-palette-posit.yml} | 2 +- pkg-py/src/brand_yaml/_defs.py | 15 +++++- pkg-py/src/brand_yaml/color.py | 50 +++++++++++++++---- pkg-py/tests/test_color.py | 17 +++---- pkg-py/tests/test_defs.py | 1 - 7 files changed, 62 insertions(+), 25 deletions(-) rename examples/{brand-color-posit-direct.yml => brand-color-direct-posit.yml} (100%) rename examples/{brand-color-posit-internal.yml => brand-color-palette-internal.yml} (97%) rename examples/{brand-color-posit-with.yml => brand-color-palette-posit.yml} (97%) diff --git a/examples/brand-color-posit-direct.yml b/examples/brand-color-direct-posit.yml similarity index 100% rename from examples/brand-color-posit-direct.yml rename to examples/brand-color-direct-posit.yml diff --git a/examples/brand-color-posit-internal.yml b/examples/brand-color-palette-internal.yml similarity index 97% rename from examples/brand-color-posit-internal.yml rename to examples/brand-color-palette-internal.yml index 8bafd141..7b4026b2 100644 --- a/examples/brand-color-posit-internal.yml +++ b/examples/brand-color-palette-internal.yml @@ -1,5 +1,5 @@ color: - with: + palette: white: "#FFFFFF" black: "#151515" blue: "#447099" diff --git a/examples/brand-color-posit-with.yml b/examples/brand-color-palette-posit.yml similarity index 97% rename from examples/brand-color-posit-with.yml rename to examples/brand-color-palette-posit.yml index f75c4f1f..93c7f5cc 100644 --- a/examples/brand-color-posit-with.yml +++ b/examples/brand-color-palette-posit.yml @@ -1,5 +1,5 @@ color: - with: + palette: white: "#FFFFFF" black: "#151515" blue: "#447099" diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 30492846..9cf0679e 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -123,6 +123,7 @@ def defs_replace_recursively( items: dict | BaseModel | None = None, level: int = 0, name: str | None = None, + exclude: str | None = None, ): """ Recursively replace string values in `items` with their definition in @@ -162,7 +163,7 @@ def defs_replace_recursively( for key in item_keys(items): value = get_value(items, key) - if value is defs or value == "with_": + if value is defs or key in set((exclude or "with_", "with_")): # We replace internal def references when resolving sibling fields continue @@ -182,7 +183,13 @@ def defs_replace_recursively( elif isinstance(value, (dict, BaseModel)): # TODO: we may want to avoid recursing into child BrandWith instances logger.debug(level_indent(f"recursing into {key}", level)) - defs_replace_recursively(defs, value, level=level + 1) + defs_replace_recursively( + defs, + value, + level=level + 1, + exclude=exclude, + name=name, + ) else: logger.debug( level_indent( @@ -224,6 +231,10 @@ def check_circular_references( path = path if path is not None else [] if not isinstance(current, (dict, BaseModel)): + if not isinstance(current, str): + raise ValueError( + "All values must be strings, dictionaries, or pydantic models." + ) return logger.debug(f"current is: {current}") diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 06018034..95fdfa7c 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -3,13 +3,18 @@ from copy import deepcopy from typing import Optional -from pydantic import ConfigDict, Field, model_validator - -from ._defs import BrandWith, defs_replace_recursively +from pydantic import ( + ConfigDict, + Field, + field_validator, + model_validator, +) + +from ._defs import check_circular_references, defs_replace_recursively from ._utils import BrandBase -class BrandColor(BrandBase, BrandWith[str]): +class BrandColor(BrandBase): """ Brand Colors @@ -69,16 +74,34 @@ class BrandColor(BrandBase, BrandWith[str]): extra="forbid", revalidate_instances="always", validate_assignment=True, + check_fields=False, ) - @model_validator(mode="after") - def resolve_with_values(self): - if self.with_ is not None: - defs_replace_recursively(self.with_, self, name="with_") + palette: dict[str, str] | None = None + + @field_validator("palette") + @classmethod + def create_brand_palette(cls, value: dict[str, str] | None): + if value is None: + return + + if not isinstance(value, dict): + raise ValueError("`palette` must be a dictionary") - _color_fields = [k for k in self.model_fields.keys() if k != "with_"] + check_circular_references(value) + # We resolve `color.palette` on load or on replacement only + # TODO: Replace with class with getter/setters + # Retain original values, return resolved values, and re-validate on update. + defs_replace_recursively(value, value, name="palette") - full_defs = deepcopy(self.with_) if self.with_ is not None else {} + return value + + @model_validator(mode="after") + def resolve_palette_values(self): + # We currently resolve + _color_fields = [k for k in self.model_fields.keys() if k != "palette"] + + full_defs = deepcopy(self.palette) if self.palette is not None else {} full_defs.update( { k: v @@ -86,7 +109,12 @@ def resolve_with_values(self): if k in _color_fields and v is not None } ) - defs_replace_recursively(full_defs, self, name="color") + defs_replace_recursively( + full_defs, + self, + name="color", + exclude="palette", + ) return self foreground: Optional[str] = Field( diff --git a/pkg-py/tests/test_color.py b/pkg-py/tests/test_color.py index ad837425..b0abcae5 100644 --- a/pkg-py/tests/test_color.py +++ b/pkg-py/tests/test_color.py @@ -1,12 +1,11 @@ from __future__ import annotations -from utils import path_examples - from brand_yaml import read_brand_yaml +from utils import path_examples def test_brand_color_posit_direct(): - brand = read_brand_yaml(path_examples("brand-color-posit-direct.yml")) + brand = read_brand_yaml(path_examples("brand-color-direct-posit.yml")) assert brand.color is not None assert brand.color.foreground == "#151515" @@ -23,7 +22,7 @@ def test_brand_color_posit_direct(): def test_brand_color_posit_with(): - brand = read_brand_yaml(path_examples("brand-color-posit-with.yml")) + brand = read_brand_yaml(path_examples("brand-color-palette-posit.yml")) # Same final values as above, but re-uses color definitions from `with` assert brand.color is not None @@ -39,8 +38,8 @@ def test_brand_color_posit_with(): assert brand.color.light == "#FFFFFF" assert brand.color.dark == "#404041" - assert brand.color.with_ is not None - assert brand.color.with_ == { + assert brand.color.palette is not None + assert brand.color.palette == { "white": "#FFFFFF", "black": "#151515", "blue": "#447099", @@ -52,7 +51,7 @@ def test_brand_color_posit_with(): def test_brand_color_posit_internal(): - brand = read_brand_yaml(path_examples("brand-color-posit-internal.yml")) + brand = read_brand_yaml(path_examples("brand-color-palette-internal.yml")) # Named theme colors are reused in BrandColor assert brand.color is not None @@ -61,8 +60,8 @@ def test_brand_color_posit_internal(): assert brand.color.info == brand.color.primary assert brand.color.light == brand.color.background - assert brand.color.with_ is not None - assert brand.color.with_ == { + assert brand.color.palette is not None + assert brand.color.palette == { "white": "#FFFFFF", "black": "#151515", "blue": "#447099", diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index 63c2d8a8..fe75bd27 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -3,7 +3,6 @@ from typing import Union import pytest - from brand_yaml._defs import ( BrandLightDarkString, BrandWith, From 2077721c4d3996d3182bc6de4c170303d1a227af Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 17 Sep 2024 12:40:44 -0400 Subject: [PATCH 054/119] feat(brand.logo.images): Rename from `logo.with` For #18 --- examples/brand-logo-full.yml | 2 +- pkg-py/src/brand_yaml/logo.py | 55 ++++++++++++++++++- .../test_logo/test_brand_logo_ex_full.json | 22 ++++++++ .../test_brand_logo_ex_light_dark.json | 10 ++++ .../test_logo/test_brand_logo_ex_simple.json | 7 +++ pkg-py/tests/test_logo.py | 22 ++++++-- 6 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json create mode 100644 pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json create mode 100644 pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json diff --git a/examples/brand-logo-full.yml b/examples/brand-logo-full.yml index e9162bc0..3b24fd1c 100644 --- a/examples/brand-logo-full.yml +++ b/examples/brand-logo-full.yml @@ -1,5 +1,5 @@ logo: - with: + images: primary: full-color.png primary-svg: full-color.svg reverse: full-color-reverse.png diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index a98762ae..408f5411 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -1,13 +1,21 @@ from __future__ import annotations +from copy import deepcopy from typing import Union -from pydantic import ConfigDict +from pydantic import ConfigDict, field_validator, model_validator -from ._defs import BrandLightDark, BrandWith +from ._defs import ( + BrandLightDark, + check_circular_references, + defs_replace_recursively, +) +from ._utils import BrandBase +BrandLogoImageType = Union[str, BrandLightDark[str]] -class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): + +class BrandLogo(BrandBase): """ Brand Logos @@ -36,8 +44,49 @@ class BrandLogo(BrandWith[Union[str, BrandLightDark[str]]]): use_attribute_docstrings=True, ) + images: dict[str, BrandLogoImageType] | None = None + # TODO: Currently we're using a string for the logo path, but we should # update this to use a validated Path or URL in the future. small: str | BrandLightDark[str] | None = None medium: str | BrandLightDark[str] | None = None large: str | BrandLightDark[str] | None = None + + @field_validator("images") + @classmethod + def validate_images( + cls, value: dict[str, BrandLogoImageType] | None + ) -> dict[str, BrandLogoImageType] | None: + if value is None: + return + + check_circular_references(value) + # We resolve `logo.images` on load or on replacement only + # TODO: Replace with class with getter/setters + # Retain original values, return resolved values, and re-validate on update. + defs_replace_recursively(value, value, name="images") + + return value + + @model_validator(mode="after") + def resolve_image_values(self): + if self.images is None: + return self + + _logo_fields = [k for k in self.model_fields.keys() if k != "images"] + + full_defs = deepcopy(self.images) if self.images is not None else {} + full_defs.update( + { + k: v + for k, v in self.model_dump().items() + if k in _logo_fields and v is not None + } + ) + defs_replace_recursively( + full_defs, + self, + name="logo", + exclude="images", + ) + return self diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json new file mode 100644 index 00000000..fcc8e195 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json @@ -0,0 +1,22 @@ +{ + "logo": { + "images": { + "black": "black.png", + "both": { + "dark": "full-color-reverse.png", + "light": "full-color.png" + }, + "icon": "favicon.png", + "primary": "full-color.png", + "primary-svg": "full-color.svg", + "reverse": "full-color-reverse.png", + "white": "white.png" + }, + "large": "full-color.svg", + "medium": { + "dark": "full-color-reverse.png", + "light": "full-color.png" + }, + "small": "black.png" + } +} diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json new file mode 100644 index 00000000..36939a75 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json @@ -0,0 +1,10 @@ +{ + "logo": { + "large": "display.svg", + "medium": { + "dark": "logo-dark.png", + "light": "logo-light.png" + }, + "small": "icon.png" + } +} diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json new file mode 100644 index 00000000..c4a3d686 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json @@ -0,0 +1,7 @@ +{ + "logo": { + "large": "display.svg", + "medium": "logo.png", + "small": "icon.png" + } +} diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index df8d7410..52146ff0 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -1,10 +1,16 @@ from __future__ import annotations -from utils import path_examples - +import pytest from brand_yaml import read_brand_yaml from brand_yaml._defs import BrandLightDark from brand_yaml.logo import BrandLogo +from syrupy.extensions.json import JSONSnapshotExtension +from utils import path_examples, pydantic_data_from_json + + +@pytest.fixture +def snapshot_json(snapshot): + return snapshot.use_extension(JSONSnapshotExtension) def test_brand_logo_single(): @@ -13,7 +19,7 @@ def test_brand_logo_single(): assert brand.logo == "posit.png" -def test_brand_logo_simple(): +def test_brand_logo_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-simple.yml")) assert isinstance(brand.logo, BrandLogo) @@ -21,8 +27,10 @@ def test_brand_logo_simple(): assert brand.logo.medium == "logo.png" assert brand.logo.large == "display.svg" + assert snapshot_json == pydantic_data_from_json(brand) -def test_brand_logo_light_dark(): + +def test_brand_logo_ex_light_dark(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-light-dark.yml")) assert isinstance(brand.logo, BrandLogo) @@ -34,8 +42,10 @@ def test_brand_logo_light_dark(): assert brand.logo.large == "display.svg" + assert snapshot_json == pydantic_data_from_json(brand) + -def test_brand_logo_full(): +def test_brand_logo_ex_full(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-full.yml")) assert isinstance(brand.logo, BrandLogo) @@ -50,3 +60,5 @@ def test_brand_logo_full(): # replace small with new value from "with" brand.logo.small = "black" assert brand.logo.small == "black.png" + + assert snapshot_json == pydantic_data_from_json(brand) From 481d8b6fca4c6747d06ae3341ddd30d491b42c69 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 17 Sep 2024 12:43:04 -0400 Subject: [PATCH 055/119] tests(brand.color): Add snapshot tests --- .../test_brand_color_ex_direct_posit.json | 15 ++++++++++++ .../test_brand_color_ex_palette_internal.json | 24 +++++++++++++++++++ .../test_brand_color_ex_palette_posit.json | 24 +++++++++++++++++++ pkg-py/tests/test_color.py | 21 ++++++++++++---- 4 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_direct_posit.json create mode 100644 pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_internal.json create mode 100644 pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_posit.json diff --git a/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_direct_posit.json b/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_direct_posit.json new file mode 100644 index 00000000..366302e3 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_direct_posit.json @@ -0,0 +1,15 @@ +{ + "color": { + "background": "#FFFFFF", + "danger": "#9A4665", + "dark": "#404041", + "foreground": "#151515", + "info": "#419599", + "light": "#FFFFFF", + "primary": "#447099", + "secondary": "#707073", + "success": "#72994E", + "tertiary": "#C2C2C4", + "warning": "#EE6331" + } +} diff --git a/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_internal.json b/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_internal.json new file mode 100644 index 00000000..c3958d77 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_internal.json @@ -0,0 +1,24 @@ +{ + "color": { + "background": "#FFFFFF", + "danger": "#9A4665", + "dark": "#404041", + "foreground": "#151515", + "info": "#447099", + "light": "#FFFFFF", + "palette": { + "black": "#151515", + "blue": "#447099", + "burgundy": "#9A4665", + "green": "#72994E", + "orange": "#EE6331", + "teal": "#419599", + "white": "#FFFFFF" + }, + "primary": "#447099", + "secondary": "#707073", + "success": "#72994E", + "tertiary": "#C2C2C4", + "warning": "#EE6331" + } +} diff --git a/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_posit.json b/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_posit.json new file mode 100644 index 00000000..666705e7 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_color/test_brand_color_ex_palette_posit.json @@ -0,0 +1,24 @@ +{ + "color": { + "background": "#FFFFFF", + "danger": "#9A4665", + "dark": "#404041", + "foreground": "#151515", + "info": "#419599", + "light": "#FFFFFF", + "palette": { + "black": "#151515", + "blue": "#447099", + "burgundy": "#9A4665", + "green": "#72994E", + "orange": "#EE6331", + "teal": "#419599", + "white": "#FFFFFF" + }, + "primary": "#447099", + "secondary": "#707073", + "success": "#72994E", + "tertiary": "#C2C2C4", + "warning": "#EE6331" + } +} diff --git a/pkg-py/tests/test_color.py b/pkg-py/tests/test_color.py index b0abcae5..2ec3e5b4 100644 --- a/pkg-py/tests/test_color.py +++ b/pkg-py/tests/test_color.py @@ -1,10 +1,17 @@ from __future__ import annotations +import pytest from brand_yaml import read_brand_yaml -from utils import path_examples +from syrupy.extensions.json import JSONSnapshotExtension +from utils import path_examples, pydantic_data_from_json -def test_brand_color_posit_direct(): +@pytest.fixture +def snapshot_json(snapshot): + return snapshot.use_extension(JSONSnapshotExtension) + + +def test_brand_color_ex_direct_posit(snapshot_json): brand = read_brand_yaml(path_examples("brand-color-direct-posit.yml")) assert brand.color is not None @@ -20,8 +27,10 @@ def test_brand_color_posit_direct(): assert brand.color.light == "#FFFFFF" assert brand.color.dark == "#404041" + assert snapshot_json == pydantic_data_from_json(brand) + -def test_brand_color_posit_with(): +def test_brand_color_ex_palette_posit(snapshot_json): brand = read_brand_yaml(path_examples("brand-color-palette-posit.yml")) # Same final values as above, but re-uses color definitions from `with` @@ -49,8 +58,10 @@ def test_brand_color_posit_with(): "burgundy": "#9A4665", } + assert snapshot_json == pydantic_data_from_json(brand) -def test_brand_color_posit_internal(): + +def test_brand_color_ex_palette_internal(snapshot_json): brand = read_brand_yaml(path_examples("brand-color-palette-internal.yml")) # Named theme colors are reused in BrandColor @@ -70,3 +81,5 @@ def test_brand_color_posit_internal(): "teal": "#419599", "burgundy": "#9A4665", } + + assert snapshot_json == pydantic_data_from_json(brand) From 719b676e9bbb2a83bd8e9b55d457b09190aa10ae Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 17 Sep 2024 13:49:52 -0400 Subject: [PATCH 056/119] feat(typography): Update typographic options For #16 See: https://github.com/posit-dev/brand-yml/issues/16#issuecomment-2344394279 --- pkg-py/src/brand_yaml/typography.py | 51 ++++++++++++++++++----------- pkg-py/tests/test_typography.py | 7 +--- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index c9b1d276..4cb912ef 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -308,42 +308,49 @@ class BrandNamedColor(RootModel): root: str -class BrandTypographyOptionsColor(BaseModel): +class BrandTypographyOptionsBackgroundColor(BaseModel): model_config = ConfigDict(populate_by_name=True) - color: BrandNamedColor | None = None background_color: BrandNamedColor | None = Field( None, alias="background-color" ) -class BrandTypographyOptionsWeight(BaseModel): - weight: BrandTypographyFontWeightSimpleType | None = None - - @field_validator("weight", mode="before") - @classmethod - def validate_weight(cls, value: int | str): - return validate_font_weight(value) +class BrandTypographyOptionsColor(BaseModel): + color: BrandNamedColor | None = None -class BrandTypographyOptionsGenericText(BrandTypographyOptionsWeight): +class BrandTypographyOptionsFamily(BaseModel): family: str | None = None - style: SingleOrList[BrandTypographyFontStyleType] | None = None + + +class BrandTypographyOptionsLineHeight(BaseModel): + line_height: float | None = Field(None, alias="line-height") class BrandTypographyOptionsSize(BaseModel): size: str | None = None -class BrandTypographyOptionsBlockText(BaseModel): - line_height: float | None = Field(None, alias="line-height") +class BrandTypographyOptionsStyle(BaseModel): + style: SingleOrList[BrandTypographyFontStyleType] | None = None + + +class BrandTypographyOptionsWeight(BaseModel): + weight: BrandTypographyFontWeightSimpleType | None = None + + @field_validator("weight", mode="before") + @classmethod + def validate_weight(cls, value: int | str): + return validate_font_weight(value) class BrandTypographyBase( BrandBase, - BrandTypographyOptionsGenericText, + BrandTypographyOptionsFamily, + BrandTypographyOptionsWeight, BrandTypographyOptionsSize, - BrandTypographyOptionsBlockText, + BrandTypographyOptionsLineHeight, BrandTypographyOptionsColor, ): model_config = ConfigDict(extra="forbid") @@ -351,8 +358,10 @@ class BrandTypographyBase( class BrandTypographyHeadings( BrandBase, - BrandTypographyOptionsGenericText, - BrandTypographyOptionsBlockText, + BrandTypographyOptionsFamily, + BrandTypographyOptionsWeight, + BrandTypographyOptionsStyle, + BrandTypographyOptionsLineHeight, BrandTypographyOptionsColor, ): model_config = ConfigDict(extra="forbid") @@ -360,7 +369,8 @@ class BrandTypographyHeadings( class BrandTypographyMonospace( BrandBase, - BrandTypographyOptionsGenericText, + BrandTypographyOptionsFamily, + BrandTypographyOptionsWeight, BrandTypographyOptionsSize, ): model_config = ConfigDict(extra="forbid") @@ -369,14 +379,16 @@ class BrandTypographyMonospace( class BrandTypographyMonospaceInline( BrandTypographyMonospace, BrandTypographyOptionsColor, + BrandTypographyOptionsBackgroundColor, ): model_config = ConfigDict(extra="forbid") class BrandTypographyMonospaceBlock( BrandTypographyMonospace, - BrandTypographyOptionsBlockText, + BrandTypographyOptionsLineHeight, BrandTypographyOptionsColor, + BrandTypographyOptionsBackgroundColor, ): model_config = ConfigDict(extra="forbid") @@ -385,6 +397,7 @@ class BrandTypographyLink( BrandBase, BrandTypographyOptionsWeight, BrandTypographyOptionsColor, + BrandTypographyOptionsBackgroundColor, ): model_config = ConfigDict(extra="forbid") diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 281e3b33..b84d695d 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -124,11 +124,9 @@ def test_brand_typography_fields_base(): assert base_fields == { "family", "weight", - "style", "size", "line_height", "color", - "background_color", } @@ -141,14 +139,13 @@ def test_brand_typography_fields_headings(): "style", "line_height", "color", - "background_color", } def test_brand_typography_fields_monospace(): fields = set(BrandTypographyMonospace.model_fields.keys()) - assert fields == {"family", "weight", "style", "size"} + assert fields == {"family", "weight", "size"} def test_brand_typography_fields_monospace_inline(): @@ -157,7 +154,6 @@ def test_brand_typography_fields_monospace_inline(): assert fields == { "family", "weight", - "style", "size", "color", "background_color", @@ -170,7 +166,6 @@ def test_brand_typography_fields_monospace_block(): assert fields == { "family", "weight", - "style", "size", "line_height", "color", From 654f5839dcd3baf15dce0c190315bd89f27889ab Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 17 Sep 2024 13:55:09 -0400 Subject: [PATCH 057/119] chore: Add comment headers and remove dead code --- pkg-py/src/brand_yaml/typography.py | 35 +++++++++++------------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 4cb912ef..08aea02f 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -19,6 +19,9 @@ from ._utils import BrandBase +# Types ------------------------------------------------------------------------ + + T = TypeVar("T") SingleOrList = Union[T, list[T]] @@ -97,6 +100,9 @@ } +# Custom Errors ---------------------------------------------------------------- + + class BrandInvalidFontWeight(ValueError): def __init__(self, value: Any): super().__init__( @@ -106,6 +112,9 @@ def __init__(self, value: Any): ) +# Fonts ------------------------------------------------------------------------ + + class BrandUnsupportedFontFileFormat(ValueError): def __init__(self, value: Any): supported = ("opentype", "truetype", "woff", "woff2") @@ -280,28 +289,7 @@ class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): url: HttpUrl = Field("https://fonts.bunny.net/") -# def brand_typography_font_discriminator( -# x: dict[str, object] | BrandTypographyFontFiles | BrandTypographyFontGoogle, -# ) -> Literal["google", "bunny", "file"]: -# if isinstance(x, BrandTypographyFontBunny): -# return "bunny" -# elif isinstance(x, BrandTypographyFontGoogle): -# return "google" -# elif isinstance(x, BrandTypographyFontFiles): -# return "file" - -# value = x.get("source") - -# if not isinstance(value, str): -# pass -# elif value in ("google", "bunny"): -# return value -# elif Path(value).suffix: -# return "file" - -# raise ValueError( -# "Unsupported font source {value!r}, must be a file path, 'google', or 'bunny'." -# ) +# Typography Options ----------------------------------------------------------- class BrandNamedColor(RootModel): @@ -404,6 +392,9 @@ class BrandTypographyLink( decoration: str | None = None +# Brand Typography ------------------------------------------------------------- + + class BrandTypography(BrandBase): model_config = ConfigDict(extra="forbid", populate_by_name=True) From d93c48839514fe64e3760343dabad2f88fdf302b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 10:00:01 -0400 Subject: [PATCH 058/119] chore: Add makefile --- Makefile | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..2f40e2a0 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +.PHONY: docs +docs: ## [docs] Build the documentation + quarto render docs + +.PHONY: docs-preview +docs-preview: ## [docs] Preview the documentation + quarto preview docs + +.PHONY: py-setup +py-setup: ## [py] Setup python environment + uv sync --all-extras + +.PHONY: py-check +py-check: py-check-tests py-check-types ## [py] Run python checks + +.PHONY: py-check-tests +py-check-tests: ## [py] Run python tests + uv run pytest + +.PHONY: py-check-types +py-check-types: ## [py] Run python type checks + uv run pyright + +.PHONY: py-update-snaps +py-update-snaps: ## [py] Update python test snapshots + uv run pytest --snapshot-update + +.PHONY: help +help: ## Show help messages for make targets + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; { \ + printf "\033[32m%-18s\033[0m", $$1; \ + if ($$2 ~ /^\[docs\]/) { \ + printf "\033[34m[docs]\033[0m%s\n", substr($$2, 7); \ + } else if ($$2 ~ /^\[py\]/) { \ + printf " \033[33m[py]\033[0m%s\n", substr($$2, 5); \ + } else if ($$2 ~ /^\[r\]/) { \ + printf " \033[31m[r]\033[0m%s\n", substr($$2, 4); \ + } else { \ + printf " %s\n", $$2; \ + } \ + }' + +.DEFAULT_GOAL := help From dcaecfbd96eec7f52a98cf5a3e2891ef280ca554 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 10:14:53 -0400 Subject: [PATCH 059/119] chore(color): Move attribute descriptions to attribute docstrings not sure if this will work with quartodoc yet --- pkg-py/src/brand_yaml/color.py | 150 +++++++++++---------------------- 1 file changed, 51 insertions(+), 99 deletions(-) diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 95fdfa7c..cbb603e5 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -5,7 +5,6 @@ from pydantic import ( ConfigDict, - Field, field_validator, model_validator, ) @@ -19,65 +18,73 @@ class BrandColor(BrandBase): Brand Colors The brand's custom color palette and theme. + """ - Attributes - ---------- - - foreground - The foreground color, used for text. - - background - The background color, used for the page or main background. + model_config = ConfigDict( + extra="forbid", + revalidate_instances="always", + validate_assignment=True, + use_attribute_docstrings=True, + ) - primary - The primary accent color, i.e. the main theme color. Typically used for - hyperlinks, active states, primary action buttons, etc. + palette: dict[str, str] | None = None - secondary - The secondary accent color. Typically used for lighter text or disabled - states. + foreground: Optional[str] = None + """The foreground color, used for text.""" - tertiary - The tertiary accent color. Typically an even lighter color, used for - hover states, accents, and wells. + background: Optional[str] = None + """The background color, used for the page or main background.""" - success - The color used for positive or successful actions and information. + primary: Optional[str] = None + """ + The primary accent color, i.e. the main theme color. Typically used for + hyperlinks, active states, primary action buttons, etc. + """ - info - The color used for neutral or informational actions and information. + secondary: Optional[str] = None + """ + The secondary accent color. Typically used for lighter text or disabled + states. + """ - warning - The color used for warning or cautionary actions and information. + tertiary: Optional[str] = None + """ + The tertiary accent color. Typically an even lighter color, used for + hover states, accents, and wells. + """ - danger - The color used for errors, dangerous actions, or negative information. + success: Optional[str] = None + """The color used for positive or successful actions and information.""" - light - A bright color, used as a high-contrast foreground color on dark - elements or low-contrast background color on light elements. + info: Optional[str] = None + """The color used for neutral or informational actions and information.""" - dark - A dark color, used as a high-contrast foreground color on light elements - or high-contrast background color on light elements. + warning: Optional[str] = None + """The color used for warning or cautionary actions and information.""" - emphasis - A color used to emphasize or highlight text or elements. + danger: Optional[str] = None + """The color used for errors, dangerous actions, or negative information.""" - link - The color used for hyperlinks. If not defined, the `primary` color is - used. + light: Optional[str] = None + """ + A bright color, used as a high-contrast foreground color on dark elements + or low-contrast background color on light elements. + """ + dark: Optional[str] = None + """ + A dark color, used as a high-contrast foreground color on light elements + or high-contrast background color on light elements. """ - model_config = ConfigDict( - extra="forbid", - revalidate_instances="always", - validate_assignment=True, - check_fields=False, - ) + emphasis: Optional[str] = None + """A color used to emphasize or highlight text or elements.""" - palette: dict[str, str] | None = None + link: Optional[str] = None + """ + The color used for hyperlinks. If not defined, the `primary` color is + used. + """ @field_validator("palette") @classmethod @@ -116,58 +123,3 @@ def resolve_palette_values(self): exclude="palette", ) return self - - foreground: Optional[str] = Field( - default=None, - description="The foreground color, used for text.", - ) - background: Optional[str] = Field( - default=None, - description="The background color, used for the page or main background.", - ) - primary: Optional[str] = Field( - default=None, - description="The primary accent color, i.e. the main theme color. Typically used for hyperlinks, active states, primary action buttons, etc.", - ) - secondary: Optional[str] = Field( - default=None, - description="The secondary accent color. Typically used for lighter text or disabled states.", - ) - tertiary: Optional[str] = Field( - default=None, - description="The tertiary accent color. Typically an even lighter color, used for hover states, accents, and wells.", - ) - - success: Optional[str] = Field( - default=None, - description="The color used for positive or successful actions and information.", - ) - info: Optional[str] = Field( - default=None, - description="The color used for neutral or informational actions and information.", - ) - warning: Optional[str] = Field( - default=None, - description="The color used for warning or cautionary actions and information.", - ) - danger: Optional[str] = Field( - default=None, - description="The color used for errors, dangerous actions, or negative information.", - ) - - light: Optional[str] = Field( - default=None, - description="A bright color, used as a high-contrast foreground color on dark elements or low-contrast background color on light elements.", - ) - dark: Optional[str] = Field( - default=None, - description="A dark color, used as a high-contrast foreground color on light elements or high-contrast background color on light elements.", - ) - emphasis: Optional[str] = Field( - default=None, - description="A color used to emphasize or highlight text or elements.", - ) - link: Optional[str] = Field( - default=None, - description="The color used for hyperlinks. If not defined, the `primary` color is used.", - ) From 19ae8e4b95248f3aec615be966fbe27572c3bfa7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 10:15:13 -0400 Subject: [PATCH 060/119] chore: uv sync --all-extras --upgrade --- uv.lock | 192 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/uv.lock b/uv.lock index f1cffb06..392b0243 100644 --- a/uv.lock +++ b/uv.lock @@ -245,14 +245,14 @@ wheels = [ [[package]] name = "importlib-metadata" -version = "8.4.0" +version = "8.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/bd/fa8ce65b0a7d4b6d143ec23b0f5fd3f7ab80121078c465bc02baeaab22dc/importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5", size = 54320 } +sdist = { url = "https://files.pythonhosted.org/packages/cd/12/33e59336dca5be0c398a7482335911a33aa0e20776128f038019f1a95f1b/importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7", size = 55304 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/14/362d31bf1076b21e1bcdcb0dc61944822ff263937b804a79231df2774d28/importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1", size = 26269 }, + { url = "https://files.pythonhosted.org/packages/a0/d9/a1e041c5e7caa9a05c925f4bdbdfb7f006d1f74996af53467bc394c97be7/importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b", size = 26514 }, ] [[package]] @@ -351,7 +351,7 @@ wheels = [ [[package]] name = "jupyter-client" -version = "8.6.2" +version = "8.6.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, @@ -361,9 +361,9 @@ dependencies = [ { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/61/3cd51dea7878691919adc34ff6ad180f13bfe25fb8c7662a9ee6dc64e643/jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df", size = 341102 } +sdist = { url = "https://files.pythonhosted.org/packages/71/22/bf9f12fdaeae18019a468b68952a60fe6dbab5d67cd2a103cac7659b41ca/jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419", size = 342019 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/d3/c4bb02580bc0db807edb9a29b2d0c56031be1ef0d804336deb2699a470f6/jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f", size = 105901 }, + { url = "https://files.pythonhosted.org/packages/11/85/b0394e0b6fcccd2c1eeefc230978a6f8cb0c5df1e4cd3e7625735a0d7d1e/jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f", size = 106105 }, ] [[package]] @@ -472,11 +472,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.3.2" +version = "4.3.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/75/a0/d7cab8409cdc7d39b037c85ac46d92434fb6595432e069251b38e5c8dd0e/platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c", size = 21276 } +sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/8b/d497999c4017b80678017ddce745cf675489c110681ad3c84a55eddfd3e7/platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617", size = 18417 }, + { url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 }, ] [[package]] @@ -546,103 +546,103 @@ wheels = [ [[package]] name = "pydantic" -version = "2.9.1" +version = "2.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/14/15/3d989541b9c8128b96d532cfd2dd10131ddcc75a807330c00feb3d42a5bd/pydantic-2.9.1.tar.gz", hash = "sha256:1363c7d975c7036df0db2b4a61f2e062fbc0aa5ab5f2772e0ffc7191a4f4bce2", size = 768511 } +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/28/fff23284071bc1ba419635c7e86561c8b9b8cf62a5bcb459b92d7625fd38/pydantic-2.9.1-py3-none-any.whl", hash = "sha256:7aff4db5fdf3cf573d4b3c30926a510a10e19a0774d38fc4967f78beb6deb612", size = 434363 }, + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, ] [[package]] name = "pydantic-core" -version = "2.23.3" +version = "2.23.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/cc/07bec3fb337ff80eacd6028745bd858b9642f61ee58cfdbfb64451c1def0/pydantic_core-2.23.3.tar.gz", hash = "sha256:3cb0f65d8b4121c1b015c60104a685feb929a29d7cf204387c7f2688c7974690", size = 402277 } +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/fb/fc7077473d843fd70bd1e09177c3225be95621881765d6f7d123036fb9c7/pydantic_core-2.23.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7f10a5d1b9281392f1bf507d16ac720e78285dfd635b05737c3911637601bae6", size = 1845897 }, - { url = "https://files.pythonhosted.org/packages/92/8c/c6f1a0f72328c5687acc0847baf806c4cb31c1a9321de70c3cbcbb37cece/pydantic_core-2.23.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c09a7885dd33ee8c65266e5aa7fb7e2f23d49d8043f089989726391dd7350c5", size = 1777037 }, - { url = "https://files.pythonhosted.org/packages/bd/fc/89e2a998218230ed8c38f0ba11d8f73947df90ac59a1e9f2fb4e1ba318a5/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6470b5a1ec4d1c2e9afe928c6cb37eb33381cab99292a708b8cb9aa89e62429b", size = 1801481 }, - { url = "https://files.pythonhosted.org/packages/d7/f3/81a5f69ea1359633876ea2283728d0afe2ed62e028d91d747dcdfabc594e/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9172d2088e27d9a185ea0a6c8cebe227a9139fd90295221d7d495944d2367700", size = 1807280 }, - { url = "https://files.pythonhosted.org/packages/7a/91/b20f5646d7ef7c2629744b49e6fb86f839aa676b1aa11fb3998371ac5860/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86fc6c762ca7ac8fbbdff80d61b2c59fb6b7d144aa46e2d54d9e1b7b0e780e01", size = 2003100 }, - { url = "https://files.pythonhosted.org/packages/89/71/59172c61f2ecd4b33276774512ef31912944429fabaa0f4483151f788a35/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0cb80fd5c2df4898693aa841425ea1727b1b6d2167448253077d2a49003e0ed", size = 2662832 }, - { url = "https://files.pythonhosted.org/packages/80/d1/c6f8e23987dc166976996a910876596635d71e529335b846880d856589fd/pydantic_core-2.23.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03667cec5daf43ac4995cefa8aaf58f99de036204a37b889c24a80927b629cec", size = 2057218 }, - { url = "https://files.pythonhosted.org/packages/ae/f3/f4381383b65cf16392aead51643fd5fb3feeb69972226d276ce5c6cfb948/pydantic_core-2.23.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:047531242f8e9c2db733599f1c612925de095e93c9cc0e599e96cf536aaf56ba", size = 1923455 }, - { url = "https://files.pythonhosted.org/packages/a1/8d/d845077d39e55763bdb99d64ef86f8961827f8896b6e58ce08ce6b255bde/pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5499798317fff7f25dbef9347f4451b91ac2a4330c6669821c8202fd354c7bee", size = 1966890 }, - { url = "https://files.pythonhosted.org/packages/53/f8/56355d7b1cf84df63f93b1a455ebb53fd9588edbb63a44fd4d801444a060/pydantic_core-2.23.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bbb5e45eab7624440516ee3722a3044b83fff4c0372efe183fd6ba678ff681fe", size = 2112163 }, - { url = "https://files.pythonhosted.org/packages/06/32/a0a7a3a318b4ae98a0e6b9e18db31fadbd3cfc46b31191e4ed4ca658e2d4/pydantic_core-2.23.3-cp310-none-win32.whl", hash = "sha256:8b5b3ed73abb147704a6e9f556d8c5cb078f8c095be4588e669d315e0d11893b", size = 1717086 }, - { url = "https://files.pythonhosted.org/packages/e3/31/38aebe234508fc30c80b4825661d3c1ef0d51b1c40a12e50855b108acd35/pydantic_core-2.23.3-cp310-none-win_amd64.whl", hash = "sha256:2b603cde285322758a0279995b5796d64b63060bfbe214b50a3ca23b5cee3e83", size = 1918933 }, - { url = "https://files.pythonhosted.org/packages/4a/60/ef8eaad365c1d94962d158633f66313e051f7b90cead647e65a96993da22/pydantic_core-2.23.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c889fd87e1f1bbeb877c2ee56b63bb297de4636661cc9bbfcf4b34e5e925bc27", size = 1843251 }, - { url = "https://files.pythonhosted.org/packages/57/f4/20aa352e03379a3b5d6c2fb951a979f70718138ea747e3f756d63dda69da/pydantic_core-2.23.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea85bda3189fb27503af4c45273735bcde3dd31c1ab17d11f37b04877859ef45", size = 1776367 }, - { url = "https://files.pythonhosted.org/packages/f1/b9/e5482ac4ea2d128925759d905fb05a08ca98e67ed1d8ab7401861997c6c8/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7f7f72f721223f33d3dc98a791666ebc6a91fa023ce63733709f4894a7dc611", size = 1800135 }, - { url = "https://files.pythonhosted.org/packages/78/9f/387353f6b6b2ed023f973cffa4e2384bb2e52d15acf5680bc70c50f6c48f/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b2b55b0448e9da68f56b696f313949cda1039e8ec7b5d294285335b53104b61", size = 1805896 }, - { url = "https://files.pythonhosted.org/packages/4f/70/9a153f19394e2ef749f586273ebcdb3de97e2fa97e175b957a8e5a2a77f9/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c24574c7e92e2c56379706b9a3f07c1e0c7f2f87a41b6ee86653100c4ce343e5", size = 2001492 }, - { url = "https://files.pythonhosted.org/packages/a5/1c/79d976846fcdcae0c657922d0f476ca287fa694e69ac1fc9d397b831e1cc/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2b05e6ccbee333a8f4b8f4d7c244fdb7a979e90977ad9c51ea31261e2085ce0", size = 2659827 }, - { url = "https://files.pythonhosted.org/packages/fd/89/cdd76ae363cabae23a4b70df50d603c81c517415ff9d5d65e72e35251cf6/pydantic_core-2.23.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2c409ce1c219c091e47cb03feb3c4ed8c2b8e004efc940da0166aaee8f9d6c8", size = 2055160 }, - { url = "https://files.pythonhosted.org/packages/1a/82/7d62c3dd4e2e101a81ac3fa138d986bfbad9727a6275fc2b4a5efb98bdbd/pydantic_core-2.23.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d965e8b325f443ed3196db890d85dfebbb09f7384486a77461347f4adb1fa7f8", size = 1922282 }, - { url = "https://files.pythonhosted.org/packages/85/e6/ef09f395c974d08674464dd3d49066612fe7cc0466ef8ce9427cadf13e5b/pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f56af3a420fb1ffaf43ece3ea09c2d27c444e7c40dcb7c6e7cf57aae764f2b48", size = 1965827 }, - { url = "https://files.pythonhosted.org/packages/a4/5e/e589474af850c77c3180b101b54bc98bf812ad09728ba2cff4989acc9734/pydantic_core-2.23.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b01a078dd4f9a52494370af21aa52964e0a96d4862ac64ff7cea06e0f12d2c5", size = 2110810 }, - { url = "https://files.pythonhosted.org/packages/e0/ff/626007d5b7ac811f9bcac6d8af3a574ccee4505c1f015d25806101842f0c/pydantic_core-2.23.3-cp311-none-win32.whl", hash = "sha256:560e32f0df04ac69b3dd818f71339983f6d1f70eb99d4d1f8e9705fb6c34a5c1", size = 1715479 }, - { url = "https://files.pythonhosted.org/packages/4f/ff/6dc33f3b71e34ef633e35d6476d245bf303fc3eaf18a00f39bb54f78faf3/pydantic_core-2.23.3-cp311-none-win_amd64.whl", hash = "sha256:c744fa100fdea0d000d8bcddee95213d2de2e95b9c12be083370b2072333a0fa", size = 1918281 }, - { url = "https://files.pythonhosted.org/packages/8f/35/6d81bc4aa7d06e716f39e2bffb0eabcbcebaf7bab94c2f8278e277ded0ea/pydantic_core-2.23.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e0ec50663feedf64d21bad0809f5857bac1ce91deded203efc4a84b31b2e4305", size = 1845250 }, - { url = "https://files.pythonhosted.org/packages/18/42/0821cd46f76406e0fe57df7a89d6af8fddb22cce755bcc2db077773c7d1a/pydantic_core-2.23.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:db6e6afcb95edbe6b357786684b71008499836e91f2a4a1e55b840955b341dbb", size = 1769993 }, - { url = "https://files.pythonhosted.org/packages/e5/55/b969088e48bd8ea588548a7194d425de74370b17b385cee4d28f5a79013d/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98ccd69edcf49f0875d86942f4418a4e83eb3047f20eb897bffa62a5d419c8fa", size = 1791250 }, - { url = "https://files.pythonhosted.org/packages/43/c1/1d460d09c012ac76b68b2a1fd426ad624724f93b40e24a9a993763f12c61/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a678c1ac5c5ec5685af0133262103defb427114e62eafeda12f1357a12140162", size = 1802530 }, - { url = "https://files.pythonhosted.org/packages/70/8e/fd3c9eda00fbdadca726f17a0f863ecd871a65b3a381b77277ae386d3bcd/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01491d8b4d8db9f3391d93b0df60701e644ff0894352947f31fff3e52bd5c801", size = 1997848 }, - { url = "https://files.pythonhosted.org/packages/f0/67/13fa22d7b09395e83721edc31bae2bd5c5e2c36a09d470c18f5d1de46958/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fcf31facf2796a2d3b7fe338fe8640aa0166e4e55b4cb108dbfd1058049bf4cb", size = 2662790 }, - { url = "https://files.pythonhosted.org/packages/fa/1b/1d689c53d15ab67cb0df1c3a2b1df873b50409581e93e4848289dce57e2f/pydantic_core-2.23.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7200fd561fb3be06827340da066df4311d0b6b8eb0c2116a110be5245dceb326", size = 2074114 }, - { url = "https://files.pythonhosted.org/packages/3d/d9/b565048609db77760b9a0900f6e0a3b2f33be47cd3c4a433f49653a0d2b5/pydantic_core-2.23.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc1636770a809dee2bd44dd74b89cc80eb41172bcad8af75dd0bc182c2666d4c", size = 1918153 }, - { url = "https://files.pythonhosted.org/packages/41/94/8ee55c51333ed8df3a6f1e73c6530c724a9a37d326e114c9e3b24faacff9/pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:67a5def279309f2e23014b608c4150b0c2d323bd7bccd27ff07b001c12c2415c", size = 1969019 }, - { url = "https://files.pythonhosted.org/packages/f7/49/0233bae5778a5526cef000447a93e8d462f4f13e2214c13c5b23d379cb25/pydantic_core-2.23.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:748bdf985014c6dd3e1e4cc3db90f1c3ecc7246ff5a3cd4ddab20c768b2f1dab", size = 2121325 }, - { url = "https://files.pythonhosted.org/packages/42/a1/2f262db2fd6f9c2c9904075a067b1764cc6f71c014be5c6c91d9de52c434/pydantic_core-2.23.3-cp312-none-win32.whl", hash = "sha256:255ec6dcb899c115f1e2a64bc9ebc24cc0e3ab097775755244f77360d1f3c06c", size = 1725252 }, - { url = "https://files.pythonhosted.org/packages/9a/00/a57937080b49500df790c4853d3e7bc605bd0784e4fcaf1a159456f37ef1/pydantic_core-2.23.3-cp312-none-win_amd64.whl", hash = "sha256:40b8441be16c1e940abebed83cd006ddb9e3737a279e339dbd6d31578b802f7b", size = 1920660 }, - { url = "https://files.pythonhosted.org/packages/e1/3c/32958c0a5d1935591b58337037a1695782e61261582d93d5a7f55441f879/pydantic_core-2.23.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6daaf5b1ba1369a22c8b050b643250e3e5efc6a78366d323294aee54953a4d5f", size = 1845068 }, - { url = "https://files.pythonhosted.org/packages/92/a1/7e628e19b78e6ffdb2c92cccbb7eca84bfd3276cee4cafcae8833452f458/pydantic_core-2.23.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d015e63b985a78a3d4ccffd3bdf22b7c20b3bbd4b8227809b3e8e75bc37f9cb2", size = 1770095 }, - { url = "https://files.pythonhosted.org/packages/bb/17/d15fd8ce143cd1abb27be924eeff3c5c0fe3b0582f703c5a5273c11e67ce/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3fc572d9b5b5cfe13f8e8a6e26271d5d13f80173724b738557a8c7f3a8a3791", size = 1790964 }, - { url = "https://files.pythonhosted.org/packages/24/cc/37feff1792f09dc33207fbad3897373229279d1973c211f9562abfdf137d/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6bd91345b5163ee7448bee201ed7dd601ca24f43f439109b0212e296eb5b423", size = 1802384 }, - { url = "https://files.pythonhosted.org/packages/44/d8/ca9acd7f5f044d9ff6e43d7f35aab4b1d5982b4773761eabe3317fc68e30/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc379c73fd66606628b866f661e8785088afe2adaba78e6bbe80796baf708a63", size = 1997824 }, - { url = "https://files.pythonhosted.org/packages/35/0f/146269dba21b10d5bf86f9a7a7bbeab4ce1db06f466a1ab5ec3dec68b409/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbdce4b47592f9e296e19ac31667daed8753c8367ebb34b9a9bd89dacaa299c9", size = 2662907 }, - { url = "https://files.pythonhosted.org/packages/5a/7d/9573f006e39cd1a7b7716d1a264e3f4f353cf0a6042c04c01c6e31666f62/pydantic_core-2.23.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3cf31edf405a161a0adad83246568647c54404739b614b1ff43dad2b02e6d5", size = 2073953 }, - { url = "https://files.pythonhosted.org/packages/7e/a5/25200aaafd1e97e2ec3c1eb4b357669dd93911f2eba252bc60b6ba884fff/pydantic_core-2.23.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e22b477bf90db71c156f89a55bfe4d25177b81fce4aa09294d9e805eec13855", size = 1917822 }, - { url = "https://files.pythonhosted.org/packages/3e/b4/ac069c58e3cee70c69f03693222cc173fdf740d20d53167bceafc1efc7ca/pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:0a0137ddf462575d9bce863c4c95bac3493ba8e22f8c28ca94634b4a1d3e2bb4", size = 1968838 }, - { url = "https://files.pythonhosted.org/packages/d1/3d/9f96bbd6212b4b0a6dc6d037e446208d3420baba2b2b81e544094b18a859/pydantic_core-2.23.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:203171e48946c3164fe7691fc349c79241ff8f28306abd4cad5f4f75ed80bc8d", size = 2121468 }, - { url = "https://files.pythonhosted.org/packages/ac/50/7399d536d6600d69059a87fff89861332c97a7b3471327a3663c7576e707/pydantic_core-2.23.3-cp313-none-win32.whl", hash = "sha256:76bdab0de4acb3f119c2a4bff740e0c7dc2e6de7692774620f7452ce11ca76c8", size = 1725373 }, - { url = "https://files.pythonhosted.org/packages/24/ba/9ac8744ab636c1161c598cc5e8261379b6b0f1d63c31242bf9d5ed41ed32/pydantic_core-2.23.3-cp313-none-win_amd64.whl", hash = "sha256:37ba321ac2a46100c578a92e9a6aa33afe9ec99ffa084424291d84e456f490c1", size = 1920594 }, - { url = "https://files.pythonhosted.org/packages/b8/9c/cb69375fd9488869c4c29edf6666050ce5c88baf755926f4121aacd9f01f/pydantic_core-2.23.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:82da2f4703894134a9f000e24965df73cc103e31e8c31906cc1ee89fde72cbd8", size = 1846402 }, - { url = "https://files.pythonhosted.org/packages/b5/7d/99d47c7084e39465781552f65889f92b1673a31c179753e476385326a3b6/pydantic_core-2.23.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dd9be0a42de08f4b58a3cc73a123f124f65c24698b95a54c1543065baca8cf0e", size = 1730388 }, - { url = "https://files.pythonhosted.org/packages/80/0d/e6be39d563846de02a1a61fa942758e6d2409f5a87bb5853f65abde2470a/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89b731f25c80830c76fdb13705c68fef6a2b6dc494402987c7ea9584fe189f5d", size = 1801656 }, - { url = "https://files.pythonhosted.org/packages/3e/4a/6d9e8ad6c95be4af18948d400284382bc7f8b00d795f2222f3f094bc4dcb/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6de1ec30c4bb94f3a69c9f5f2182baeda5b809f806676675e9ef6b8dc936f28", size = 1807884 }, - { url = "https://files.pythonhosted.org/packages/a9/09/751832a0938384cf78ce0353d38ef350c9ecbf2ebd5dc7ff0b3b3a0f8bfd/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb68b41c3fa64587412b104294b9cbb027509dc2f6958446c502638d481525ef", size = 2003488 }, - { url = "https://files.pythonhosted.org/packages/4b/1f/77c720b6ca179f59c44a5698163b38be58e735974db28d761b31462da42e/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c3980f2843de5184656aab58698011b42763ccba11c4a8c35936c8dd6c7068c", size = 2664470 }, - { url = "https://files.pythonhosted.org/packages/47/71/5aa475102a31edc15bb0df9a6627de64f62b11be99be49f2a4a0d2a19eea/pydantic_core-2.23.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94f85614f2cba13f62c3c6481716e4adeae48e1eaa7e8bac379b9d177d93947a", size = 2057855 }, - { url = "https://files.pythonhosted.org/packages/d2/66/15d6378783e2ede05416194848030b35cf732d84cf6cb8897aa916f628a6/pydantic_core-2.23.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:510b7fb0a86dc8f10a8bb43bd2f97beb63cffad1203071dc434dac26453955cd", size = 1923691 }, - { url = "https://files.pythonhosted.org/packages/6e/c5/7172805d806012aaff6547d2c819a98bc318313d36a9b10cd48241d85fb1/pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1eba2f7ce3e30ee2170410e2171867ea73dbd692433b81a93758ab2de6c64835", size = 1967678 }, - { url = "https://files.pythonhosted.org/packages/2b/51/6e1f5b06a3e70de9ac4d14d5ddf74564c2831ed403bb86808742c26d4240/pydantic_core-2.23.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b259fd8409ab84b4041b7b3f24dcc41e4696f180b775961ca8142b5b21d0e70", size = 2112758 }, - { url = "https://files.pythonhosted.org/packages/3f/e5/1ee8f68f9425728541edb9df26702f95f8243c9e42f405b2a972c64edb1b/pydantic_core-2.23.3-cp39-none-win32.whl", hash = "sha256:40d9bd259538dba2f40963286009bf7caf18b5112b19d2b55b09c14dde6db6a7", size = 1716954 }, - { url = "https://files.pythonhosted.org/packages/96/67/663492ab80a625d07ca4abd3178023fa79a9f6fa1df4acc3213bff371e9d/pydantic_core-2.23.3-cp39-none-win_amd64.whl", hash = "sha256:5a8cd3074a98ee70173a8633ad3c10e00dcb991ecec57263aacb4095c5efb958", size = 1921529 }, - { url = "https://files.pythonhosted.org/packages/c0/2d/1f4ec8614225b516366f6c4c49d55ec42ebb93004c0bc9a3e0d21d0ed3c0/pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f399e8657c67313476a121a6944311fab377085ca7f490648c9af97fc732732d", size = 1834597 }, - { url = "https://files.pythonhosted.org/packages/4d/f0/665d4cd60147992b1da0f5a9d1fd7f309c7f12999e3a494c4898165c64ab/pydantic_core-2.23.3-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:6b5547d098c76e1694ba85f05b595720d7c60d342f24d5aad32c3049131fa5c4", size = 1721339 }, - { url = "https://files.pythonhosted.org/packages/a7/02/7b85ae2c3452e6b9f43b89482dc2a2ba771c31d86d93c2a5a250870b243b/pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0dda0290a6f608504882d9f7650975b4651ff91c85673341789a476b1159f211", size = 1794316 }, - { url = "https://files.pythonhosted.org/packages/61/09/f0fde8a9d66f37f3e08e03965a9833d71c4b5fb0287d8f625f88d79dfcd6/pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65b6e5da855e9c55a0c67f4db8a492bf13d8d3316a59999cfbaf98cc6e401961", size = 1944713 }, - { url = "https://files.pythonhosted.org/packages/61/2b/0bfe144cac991700dbeaff620fed38b0565352acb342f90374ebf1350084/pydantic_core-2.23.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09e926397f392059ce0afdcac920df29d9c833256354d0c55f1584b0b70cf07e", size = 1916385 }, - { url = "https://files.pythonhosted.org/packages/02/4f/7d1b8a28e4a1dd96cdde9e220627abd4d3a7860eb79cc682ccf828cf93e4/pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:87cfa0ed6b8c5bd6ae8b66de941cece179281239d482f363814d2b986b79cedc", size = 1959666 }, - { url = "https://files.pythonhosted.org/packages/5d/9a/b2c520ef627001c68cf23990b2de42ba66eae58a3f56f13375ae9aecb88d/pydantic_core-2.23.3-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e61328920154b6a44d98cabcb709f10e8b74276bc709c9a513a8c37a18786cc4", size = 2103742 }, - { url = "https://files.pythonhosted.org/packages/cd/43/b9a88a4e6454fcad63317e3dade687b68ae7d9f324c868411b1ea70218b3/pydantic_core-2.23.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce3317d155628301d649fe5e16a99528d5680af4ec7aa70b90b8dacd2d725c9b", size = 1916507 }, - { url = "https://files.pythonhosted.org/packages/e7/52/fd89a422e922174728341b594612e9c727f5c07c55e3e436dc3dd626f52d/pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e89513f014c6be0d17b00a9a7c81b1c426f4eb9224b15433f3d98c1a071f8433", size = 1835707 }, - { url = "https://files.pythonhosted.org/packages/be/14/07f8fa279d8c7b414c7e547f868dd1b9f8e76f248f49fb44c2312be62cb0/pydantic_core-2.23.3-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4f62c1c953d7ee375df5eb2e44ad50ce2f5aff931723b398b8bc6f0ac159791a", size = 1722073 }, - { url = "https://files.pythonhosted.org/packages/18/02/09c3ec4f9b270fd5af8f142b5547c396a1cb2aba6721b374f77a60e4bae4/pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2718443bc671c7ac331de4eef9b673063b10af32a0bb385019ad61dcf2cc8f6c", size = 1794805 }, - { url = "https://files.pythonhosted.org/packages/e7/5c/2ab3689816702554ac73ea5c435030be5461180d5b18f252ea7890774227/pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d90e08b2727c5d01af1b5ef4121d2f0c99fbee692c762f4d9d0409c9da6541", size = 1945670 }, - { url = "https://files.pythonhosted.org/packages/12/ef/c16db2dc939e2686b63a1cd19e80fda55fff95b7411cc3a34ca7d7d2463e/pydantic_core-2.23.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2b676583fc459c64146debea14ba3af54e540b61762dfc0613dc4e98c3f66eeb", size = 1916745 }, - { url = "https://files.pythonhosted.org/packages/00/58/c55081fdfc1a1c26c4d90555c013bbb6193721147154b5ba3dff16c36b96/pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:50e4661f3337977740fdbfbae084ae5693e505ca2b3130a6d4eb0f2281dc43b8", size = 1960193 }, - { url = "https://files.pythonhosted.org/packages/10/0e/664177152393180ca06ed393a3d4b16804d0a98ce9ccb460c1d29950ab77/pydantic_core-2.23.3-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:68f4cf373f0de6abfe599a38307f4417c1c867ca381c03df27c873a9069cda25", size = 2104209 }, - { url = "https://files.pythonhosted.org/packages/88/6a/df8adefd9d1052c72ee98b8c50a5eb042cdb3f2fea1f4f58a16046bdac02/pydantic_core-2.23.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:59d52cf01854cb26c46958552a21acb10dd78a52aa34c86f284e66b209db8cab", size = 1917304 }, + { url = "https://files.pythonhosted.org/packages/5c/8b/d3ae387f66277bd8104096d6ec0a145f4baa2966ebb2cad746c0920c9526/pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b", size = 1867835 }, + { url = "https://files.pythonhosted.org/packages/46/76/f68272e4c3a7df8777798282c5e47d508274917f29992d84e1898f8908c7/pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166", size = 1776689 }, + { url = "https://files.pythonhosted.org/packages/cc/69/5f945b4416f42ea3f3bc9d2aaec66c76084a6ff4ff27555bf9415ab43189/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb", size = 1800748 }, + { url = "https://files.pythonhosted.org/packages/50/ab/891a7b0054bcc297fb02d44d05c50e68154e31788f2d9d41d0b72c89fdf7/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916", size = 1806469 }, + { url = "https://files.pythonhosted.org/packages/31/7c/6e3fa122075d78f277a8431c4c608f061881b76c2b7faca01d317ee39b5d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07", size = 2002246 }, + { url = "https://files.pythonhosted.org/packages/ad/6f/22d5692b7ab63fc4acbc74de6ff61d185804a83160adba5e6cc6068e1128/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232", size = 2659404 }, + { url = "https://files.pythonhosted.org/packages/11/ac/1e647dc1121c028b691028fa61a4e7477e6aeb5132628fde41dd34c1671f/pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2", size = 2053940 }, + { url = "https://files.pythonhosted.org/packages/91/75/984740c17f12c3ce18b5a2fcc4bdceb785cce7df1511a4ce89bca17c7e2d/pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f", size = 1921437 }, + { url = "https://files.pythonhosted.org/packages/a0/74/13c5f606b64d93f0721e7768cd3e8b2102164866c207b8cd6f90bb15d24f/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3", size = 1966129 }, + { url = "https://files.pythonhosted.org/packages/18/03/9c4aa5919457c7b57a016c1ab513b1a926ed9b2bb7915bf8e506bf65c34b/pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071", size = 2110908 }, + { url = "https://files.pythonhosted.org/packages/92/2c/053d33f029c5dc65e5cf44ff03ceeefb7cce908f8f3cca9265e7f9b540c8/pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119", size = 1735278 }, + { url = "https://files.pythonhosted.org/packages/de/81/7dfe464eca78d76d31dd661b04b5f2036ec72ea8848dd87ab7375e185c23/pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/5d/30/890a583cd3f2be27ecf32b479d5d615710bb926d92da03e3f7838ff3e58b/pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8", size = 1865160 }, + { url = "https://files.pythonhosted.org/packages/1d/9a/b634442e1253bc6889c87afe8bb59447f106ee042140bd57680b3b113ec7/pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d", size = 1776777 }, + { url = "https://files.pythonhosted.org/packages/75/9a/7816295124a6b08c24c96f9ce73085032d8bcbaf7e5a781cd41aa910c891/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e", size = 1799244 }, + { url = "https://files.pythonhosted.org/packages/a9/8f/89c1405176903e567c5f99ec53387449e62f1121894aa9fc2c4fdc51a59b/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607", size = 1805307 }, + { url = "https://files.pythonhosted.org/packages/d5/a5/1a194447d0da1ef492e3470680c66048fef56fc1f1a25cafbea4bc1d1c48/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd", size = 2000663 }, + { url = "https://files.pythonhosted.org/packages/13/a5/1df8541651de4455e7d587cf556201b4f7997191e110bca3b589218745a5/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea", size = 2655941 }, + { url = "https://files.pythonhosted.org/packages/44/31/a3899b5ce02c4316865e390107f145089876dff7e1dfc770a231d836aed8/pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e", size = 2052105 }, + { url = "https://files.pythonhosted.org/packages/1b/aa/98e190f8745d5ec831f6d5449344c48c0627ac5fed4e5340a44b74878f8e/pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b", size = 1919967 }, + { url = "https://files.pythonhosted.org/packages/ae/35/b6e00b6abb2acfee3e8f85558c02a0822e9a8b2f2d812ea8b9079b118ba0/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0", size = 1964291 }, + { url = "https://files.pythonhosted.org/packages/13/46/7bee6d32b69191cd649bbbd2361af79c472d72cb29bb2024f0b6e350ba06/pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64", size = 2109666 }, + { url = "https://files.pythonhosted.org/packages/39/ef/7b34f1b122a81b68ed0a7d0e564da9ccdc9a2924c8d6c6b5b11fa3a56970/pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f", size = 1732940 }, + { url = "https://files.pythonhosted.org/packages/2f/76/37b7e76c645843ff46c1d73e046207311ef298d3f7b2f7d8f6ac60113071/pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3", size = 1916804 }, + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, + { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, + { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, + { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, + { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, + { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, + { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, + { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, + { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, + { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, + { url = "https://files.pythonhosted.org/packages/7a/04/2580b2deaae37b3e30fc30c54298be938b973990b23612d6b61c7bdd01c7/pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a", size = 1868200 }, + { url = "https://files.pythonhosted.org/packages/39/6e/e311bd0751505350f0cdcee3077841eb1f9253c5a1ddbad048cd9fbf7c6e/pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36", size = 1749316 }, + { url = "https://files.pythonhosted.org/packages/d0/b4/95b5eb47c6dc8692508c3ca04a1f8d6f0884c9dacb34cf3357595cbe73be/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b", size = 1800880 }, + { url = "https://files.pythonhosted.org/packages/da/79/41c4f817acd7f42d94cd1e16526c062a7b089f66faed4bd30852314d9a66/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323", size = 1807077 }, + { url = "https://files.pythonhosted.org/packages/fb/53/d13d1eb0a97d5c06cf7a225935d471e9c241afd389a333f40c703f214973/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3", size = 2002859 }, + { url = "https://files.pythonhosted.org/packages/53/7d/6b8a1eff453774b46cac8c849e99455b27167971a003212f668e94bc4c9c/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df", size = 2661437 }, + { url = "https://files.pythonhosted.org/packages/6c/ea/8820f57f0b46e6148ee42d8216b15e8fe3b360944284bbc705bf34fac888/pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c", size = 2054404 }, + { url = "https://files.pythonhosted.org/packages/0f/36/d4ae869e473c3c7868e1cd1e2a1b9e13bce5cd1a7d287f6ac755a0b1575e/pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55", size = 1921680 }, + { url = "https://files.pythonhosted.org/packages/0d/f8/eed5c65b80c4ac4494117e2101973b45fc655774ef647d17dde40a70f7d2/pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040", size = 1966093 }, + { url = "https://files.pythonhosted.org/packages/e8/c8/1d42ce51d65e571ab53d466cae83434325a126811df7ce4861d9d97bee4b/pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605", size = 2111437 }, + { url = "https://files.pythonhosted.org/packages/aa/c9/7fea9d13383c2ec6865919e09cffe44ab77e911eb281b53a4deaafd4c8e8/pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6", size = 1735049 }, + { url = "https://files.pythonhosted.org/packages/98/95/dd7045c4caa2b73d0bf3b989d66b23cfbb7a0ef14ce99db15677a000a953/pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29", size = 1920180 }, + { url = "https://files.pythonhosted.org/packages/13/a9/5d582eb3204464284611f636b55c0a7410d748ff338756323cb1ce721b96/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5", size = 1857135 }, + { url = "https://files.pythonhosted.org/packages/2c/57/faf36290933fe16717f97829eabfb1868182ac495f99cf0eda9f59687c9d/pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec", size = 1740583 }, + { url = "https://files.pythonhosted.org/packages/91/7c/d99e3513dc191c4fec363aef1bf4c8af9125d8fa53af7cb97e8babef4e40/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480", size = 1793637 }, + { url = "https://files.pythonhosted.org/packages/29/18/812222b6d18c2d13eebbb0f7cdc170a408d9ced65794fdb86147c77e1982/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068", size = 1941963 }, + { url = "https://files.pythonhosted.org/packages/0f/36/c1f3642ac3f05e6bb4aec3ffc399fa3f84895d259cf5f0ce3054b7735c29/pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801", size = 1915332 }, + { url = "https://files.pythonhosted.org/packages/f7/ca/9c0854829311fb446020ebb540ee22509731abad886d2859c855dd29b904/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728", size = 1957926 }, + { url = "https://files.pythonhosted.org/packages/c0/1c/7836b67c42d0cd4441fcd9fafbf6a027ad4b79b6559f80cf11f89fd83648/pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433", size = 2100342 }, + { url = "https://files.pythonhosted.org/packages/a9/f9/b6bcaf874f410564a78908739c80861a171788ef4d4f76f5009656672dfe/pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753", size = 1920344 }, + { url = "https://files.pythonhosted.org/packages/32/fd/ac9cdfaaa7cf2d32590b807d900612b39acb25e5527c3c7e482f0553025b/pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21", size = 1857850 }, + { url = "https://files.pythonhosted.org/packages/08/fe/038f4b2bcae325ea643c8ad353191187a4c92a9c3b913b139289a6f2ef04/pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb", size = 1740265 }, + { url = "https://files.pythonhosted.org/packages/51/14/b215c9c3cbd1edaaea23014d4b3304260823f712d3fdee52549b19b25d62/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59", size = 1793912 }, + { url = "https://files.pythonhosted.org/packages/62/de/2c3ad79b63ba564878cbce325be725929ba50089cd5156f89ea5155cb9b3/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577", size = 1942870 }, + { url = "https://files.pythonhosted.org/packages/cb/55/c222af19e4644c741b3f3fe4fd8bbb6b4cdca87d8a49258b61cf7826b19e/pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744", size = 1915610 }, + { url = "https://files.pythonhosted.org/packages/c4/7a/9a8760692a6f76bb54bcd43f245ff3d8b603db695899bbc624099c00af80/pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef", size = 1958403 }, + { url = "https://files.pythonhosted.org/packages/4c/91/9b03166feb914bb5698e2f6499e07c2617e2eebf69f9374d0358d7eb2009/pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8", size = 2101154 }, + { url = "https://files.pythonhosted.org/packages/1d/d9/1d7ecb98318da4cb96986daaf0e20d66f1651d0aeb9e2d4435b916ce031d/pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e", size = 1920855 }, ] [[package]] @@ -656,14 +656,14 @@ wheels = [ [[package]] name = "pyright" -version = "1.1.380" +version = "1.1.381" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nodeenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/bc/3bb71d02125dae6730d64bb32571c0eda5e5d86483b198b7f6dd82c49c6f/pyright-1.1.380.tar.gz", hash = "sha256:e6ceb1a5f7e9f03106e0aa1d6fbb4d97735a5e7ffb59f3de6b2db590baf935b2", size = 17487 } +sdist = { url = "https://files.pythonhosted.org/packages/10/f4/8e2374423280cfb221a8eba3cb13d39276a05e592fea36bc06d5feb18c33/pyright-1.1.381.tar.gz", hash = "sha256:314cf0c1351c189524fb10c7ac20688ecd470e8cc505c394d642c9c80bf7c3a5", size = 17488 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/fc/527c58a3b66d5a16dbb124e1c40cf9f207147f5a36f224fb29eb786a0763/pyright-1.1.380-py3-none-any.whl", hash = "sha256:a6404392053d8848bacc7aebcbd9d318bb46baf1a1a000359305481920f43879", size = 18219 }, + { url = "https://files.pythonhosted.org/packages/2f/c0/fec7607edc2459816c49815cd5dac67b28c702ed497102118cdc2757cc8d/pyright-1.1.381-py3-none-any.whl", hash = "sha256:5dc0aa80a265675d36abab59c674ae01dbe476714f91845b61b841d34aa99081", size = 18221 }, ] [[package]] @@ -1108,9 +1108,9 @@ wheels = [ [[package]] name = "zipp" -version = "3.20.1" +version = "3.20.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/8b/1239a3ef43a0d0ebdca623fb6413bc7702c321400c5fdd574f0b7aa0fbb4/zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b", size = 23848 } +sdist = { url = "https://files.pythonhosted.org/packages/54/bf/5c0000c44ebc80123ecbdddba1f5dcd94a5ada602a9c225d84b5aaa55e86/zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29", size = 24199 } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/9e/c96f7a4cd0bf5625bb409b7e61e99b1130dc63a98cb8b24aeabae62d43e8/zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064", size = 8988 }, + { url = "https://files.pythonhosted.org/packages/62/8b/5ba542fa83c90e09eac972fc9baca7a88e7e7ca4b221a89251954019308b/zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350", size = 9200 }, ] From 535330b4ad0bba1e8ea27b62a0c43c900b3aae1b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 10:33:55 -0400 Subject: [PATCH 061/119] chore: Move dev deps to `tool.uv` section --- pyproject.toml | 14 +++++++------- uv.lock | 28 +++++++++++++++------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index dd5c1933..c6209d3f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,18 +11,18 @@ dependencies = [ "eval-type-backport>=0.2.0", ] -[project.optional-dependencies] -quarto = [ +[tool.uv] +dev-dependencies = [ + # Actual dev deps + "pyright>=1.1.379", + "pytest>=8.3.2", + "syrupy>=4.7.1", + # Dev work with Quarto + Positron "pyyaml>=6.0.2", "nbformat>=5.10.4", "nbclient>=0.10.0", "ipykernel>=6.29.5", ] -dev = [ - "pyright>=1.1.379", - "pytest>=8.3.2", - "syrupy>=4.7.1", -] [build-system] requires = ["hatchling"] diff --git a/uv.lock b/uv.lock index 392b0243..93dbe65f 100644 --- a/uv.lock +++ b/uv.lock @@ -55,32 +55,34 @@ dependencies = [ { name = "ruamel-yaml" }, ] -[package.optional-dependencies] +[package.dev-dependencies] dev = [ - { name = "pyright" }, - { name = "pytest" }, - { name = "syrupy" }, -] -quarto = [ { name = "ipykernel" }, { name = "nbclient" }, { name = "nbformat" }, + { name = "pyright" }, + { name = "pytest" }, { name = "pyyaml" }, + { name = "syrupy" }, ] [package.metadata] requires-dist = [ { name = "eval-type-backport", specifier = ">=0.2.0" }, - { name = "ipykernel", marker = "extra == 'quarto'", specifier = ">=6.29.5" }, { name = "jsonschema", specifier = ">=4.23.0" }, - { name = "nbclient", marker = "extra == 'quarto'", specifier = ">=0.10.0" }, - { name = "nbformat", marker = "extra == 'quarto'", specifier = ">=5.10.4" }, { name = "pydantic", specifier = ">=2.8.2" }, - { name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.379" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.2" }, - { name = "pyyaml", marker = "extra == 'quarto'", specifier = ">=6.0.2" }, { name = "ruamel-yaml", specifier = ">=0.18.6" }, - { name = "syrupy", marker = "extra == 'dev'", specifier = ">=4.7.1" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "nbclient", specifier = ">=0.10.0" }, + { name = "nbformat", specifier = ">=5.10.4" }, + { name = "pyright", specifier = ">=1.1.379" }, + { name = "pytest", specifier = ">=8.3.2" }, + { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "syrupy", specifier = ">=4.7.1" }, ] [[package]] From 578ff1ba9a994b7b8797671e58c25101dc4f3fc3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 10:46:06 -0400 Subject: [PATCH 062/119] chore: Add ruff to project makefile --- Makefile | 18 +++++++++++++++++- pyproject.toml | 3 ++- uv.lock | 27 +++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2f40e2a0..a08b8496 100644 --- a/Makefile +++ b/Makefile @@ -11,18 +11,34 @@ py-setup: ## [py] Setup python environment uv sync --all-extras .PHONY: py-check -py-check: py-check-tests py-check-types ## [py] Run python checks +py-check: py-check-tests py-check-format py-check-types ## [py] Run python checks .PHONY: py-check-tests py-check-tests: ## [py] Run python tests + @echo "" + @echo "🧪 Running tests with pytest" uv run pytest .PHONY: py-check-types py-check-types: ## [py] Run python type checks + @echo "" + @echo "📝 Checking types with pyright" uv run pyright +.PHONY: py-check-format +py-check-format: + @echo "" + @echo "📐 Checking format with ruff" + uv run ruff check pkg-py --config pyproject.toml + +.PHONY: py-format +py-format: ## [py] Format python code + uv run ruff check --fix pkg-py --config pyproject.toml + uv run ruff format pkg-py --config pyproject.toml + .PHONY: py-update-snaps py-update-snaps: ## [py] Update python test snapshots + @echo "📸 Updating pytest snapshots" uv run pytest --snapshot-update .PHONY: help diff --git a/pyproject.toml b/pyproject.toml index c6209d3f..c532758b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,7 @@ dev-dependencies = [ "pyright>=1.1.379", "pytest>=8.3.2", "syrupy>=4.7.1", + "ruff>=0.6.5", # Dev work with Quarto + Positron "pyyaml>=6.0.2", "nbformat>=5.10.4", @@ -36,7 +37,7 @@ packages = ["pkg-py/src/brand_yaml"] [tool.pyright] include = ["pkg-py"] -exclude = ["pkg-py/_dev", "pkg-py/.venv", "pkg-py/src/brand_yaml/_brand.py"] +exclude = ["pkg-py/_dev", "pkg-py/.venv"] [tool.ruff] src = ["pkg-py"] diff --git a/uv.lock b/uv.lock index 93dbe65f..18fa7277 100644 --- a/uv.lock +++ b/uv.lock @@ -63,6 +63,7 @@ dev = [ { name = "pyright" }, { name = "pytest" }, { name = "pyyaml" }, + { name = "ruff" }, { name = "syrupy" }, ] @@ -82,6 +83,7 @@ dev = [ { name = "pyright", specifier = ">=1.1.379" }, { name = "pytest", specifier = ">=8.3.2" }, { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "ruff", specifier = ">=0.6.5" }, { name = "syrupy", specifier = ">=4.7.1" }, ] @@ -1019,6 +1021,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/82/e9bb3a3a2268987b7bc472c5c26b420757e04db0d0408e6626d07e388e4c/ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15", size = 118400 }, ] +[[package]] +name = "ruff" +version = "0.6.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/3f/29b2d3d90f811f6fb5b90242309f4668cd8c2482aab86ffc23099000545b/ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb", size = 2476127 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/05/cc62df44b5a0271b29f11d687aa89e85943e0d26e5bb773dbc1456d9885d/ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748", size = 9770988 }, + { url = "https://files.pythonhosted.org/packages/09/3d/89dac56ab7053d5b7cba723c9cae1a29b7a2978174c67e2441525ee00343/ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69", size = 9423303 }, + { url = "https://files.pythonhosted.org/packages/70/76/dc04654d26beace866a3c9e0c87112304e3d6406e1ee8ca0d9bebbd82d91/ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680", size = 9134078 }, + { url = "https://files.pythonhosted.org/packages/da/52/6a492cffcd2c6e243043937ab52811b6ebb10cb5b77a68cc98e7676ceaef/ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f", size = 10105094 }, + { url = "https://files.pythonhosted.org/packages/59/7c/fd76a583ae59a276537d71921d616a83ec7774027d0812049afb6af8a07f/ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972", size = 9542751 }, + { url = "https://files.pythonhosted.org/packages/56/5b/4e8928fa11412b16ecf7d7755fe45db6dfa7abce32841f6aec33bae3a7da/ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200", size = 10358844 }, + { url = "https://files.pythonhosted.org/packages/bd/a8/315ea8f71b111c8fb2b681c88a3e7a707d74308eb1435dc6ee3e6637a286/ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276", size = 11075199 }, + { url = "https://files.pythonhosted.org/packages/d9/1c/3a3728d42db52bfe418d8c913b453531766be1383719573f2458e8b59990/ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810", size = 10661186 }, + { url = "https://files.pythonhosted.org/packages/d4/0c/ae25e213461aab274822081923d747f02929d71843c42b8f56018a7ec636/ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178", size = 11747444 }, + { url = "https://files.pythonhosted.org/packages/c4/e3/9d0ff218c7663ab9d53abe02911bec03d32b8ced7f78c1c49c2af84903a2/ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253", size = 10266302 }, + { url = "https://files.pythonhosted.org/packages/ac/03/f158cc24120bf277b0cd7906ba509a2db74531003663500a0d1781cd7448/ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19", size = 10104976 }, + { url = "https://files.pythonhosted.org/packages/91/d0/0bacdffc234e588ec05834186ad11ec8281a6ca598d0106892497bbcfa44/ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c", size = 9625374 }, + { url = "https://files.pythonhosted.org/packages/1a/ad/721003cde8abd9f50bff74acbcb21852531036451d48a1abddba4dd84025/ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae", size = 9959661 }, + { url = "https://files.pythonhosted.org/packages/37/84/8d70a3eacaacb65b4bb1461fc1a59e37ff165152b7e507692109117c877f/ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc", size = 10327408 }, + { url = "https://files.pythonhosted.org/packages/54/7e/6b0a9ab30428a9e3d9607f6dd2e4fb743594d42bd1b6ba7b7b239acda921/ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5", size = 8012512 }, + { url = "https://files.pythonhosted.org/packages/d8/88/176f50162a219e3039f21e9e4323869fc62bf8d3afb4147a390d6c744bd8/ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9", size = 8804438 }, + { url = "https://files.pythonhosted.org/packages/67/a0/1b488bbe35a7ff8296fdea1ec1a9c2676cecc7e42bda63860f9397d59140/ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0", size = 8179780 }, +] + [[package]] name = "six" version = "1.16.0" From c2182ac11544efab5df2c6f5d18210a5e05674f3 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 11:11:55 -0400 Subject: [PATCH 063/119] chore: ruff check --fix --- pkg-py/tests/test_meta.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index 1c098a83..211785d4 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -1,10 +1,9 @@ from __future__ import annotations import pytest -from utils import path_examples - from brand_yaml import read_brand_yaml from brand_yaml.meta import BrandMeta, BrandMetaLink +from utils import path_examples def test_brand_meta(): From e61734b6a8b6f1bb5f61558e4f54fc2d5d39b3b5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 11:12:07 -0400 Subject: [PATCH 064/119] chore: Add package checking with tox --- Makefile | 6 +++ pyproject.toml | 30 +++++++++-- uv.lock | 142 ++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 169 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index a08b8496..c5901905 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,12 @@ py-setup: ## [py] Setup python environment .PHONY: py-check py-check: py-check-tests py-check-format py-check-types ## [py] Run python checks +.PHONY: py-check-tox +py-check-tox: ## [py] Run python 3.9 - 3.12 checks with tox + @echo "" + @echo "🔄 Running tests and type checking with tox for Python 3.9--3.12" + uv run tox run-parallel + .PHONY: py-check-tests py-check-tests: ## [py] Run python tests @echo "" diff --git a/pyproject.toml b/pyproject.toml index c532758b..36b0ccdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,13 +11,18 @@ dependencies = [ "eval-type-backport>=0.2.0", ] -[tool.uv] -dev-dependencies = [ - # Actual dev deps +[project.optional-dependencies] +test = [ "pyright>=1.1.379", "pytest>=8.3.2", "syrupy>=4.7.1", +] + +[tool.uv] +dev-dependencies = [ + # Actual dev deps "ruff>=0.6.5", + "tox-uv>=1.11.4", # Dev work with Quarto + Positron "pyyaml>=6.0.2", "nbformat>=5.10.4", @@ -39,6 +44,25 @@ packages = ["pkg-py/src/brand_yaml"] include = ["pkg-py"] exclude = ["pkg-py/_dev", "pkg-py/.venv"] +[tool.tox] +legacy_tox_ini = """ +[tox] +env_list = type-py3{9,10,11,12}, py3{9,10,11,12} +isolated_build = True + +[testenv] +package = wheel +wheel_build_env = .pkg +commands = pytest + +[testenv:type] +deps = + pyright + pytest + syrupy +commands = pyright +""" + [tool.ruff] src = ["pkg-py"] exclude = [ diff --git a/uv.lock b/uv.lock index 18fa7277..0d7b6382 100644 --- a/uv.lock +++ b/uv.lock @@ -55,16 +55,21 @@ dependencies = [ { name = "ruamel-yaml" }, ] +[package.optional-dependencies] +test = [ + { name = "pyright" }, + { name = "pytest" }, + { name = "syrupy" }, +] + [package.dev-dependencies] dev = [ { name = "ipykernel" }, { name = "nbclient" }, { name = "nbformat" }, - { name = "pyright" }, - { name = "pytest" }, { name = "pyyaml" }, { name = "ruff" }, - { name = "syrupy" }, + { name = "tox-uv" }, ] [package.metadata] @@ -72,7 +77,10 @@ requires-dist = [ { name = "eval-type-backport", specifier = ">=0.2.0" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "pydantic", specifier = ">=2.8.2" }, + { name = "pyright", marker = "extra == 'test'", specifier = ">=1.1.379" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.2" }, { name = "ruamel-yaml", specifier = ">=0.18.6" }, + { name = "syrupy", marker = "extra == 'test'", specifier = ">=4.7.1" }, ] [package.metadata.requires-dev] @@ -80,11 +88,18 @@ dev = [ { name = "ipykernel", specifier = ">=6.29.5" }, { name = "nbclient", specifier = ">=0.10.0" }, { name = "nbformat", specifier = ">=5.10.4" }, - { name = "pyright", specifier = ">=1.1.379" }, - { name = "pytest", specifier = ">=8.3.2" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "ruff", specifier = ">=0.6.5" }, - { name = "syrupy", specifier = ">=4.7.1" }, + { name = "tox-uv", specifier = ">=1.11.4" }, +] + +[[package]] +name = "cachetools" +version = "5.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/38/a0f315319737ecf45b4319a8cd1f3a908e29d9277b46942263292115eee7/cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a", size = 27661 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/07/14f8ad37f2d12a5ce41206c21820d8cb6561b728e51fad4530dff0552a67/cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292", size = 9524 }, ] [[package]] @@ -156,6 +171,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/52/b08750ce0bce45c143e1b5d7357ee8c55341b52bdef4b0f081af1eb248c2/cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662", size = 181290 }, ] +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + [[package]] name = "colorama" version = "0.4.6" @@ -211,6 +235,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 }, ] +[[package]] +name = "distlib" +version = "0.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/91/e2df406fb4efacdf46871c25cde65d3c6ee5e173b7e5a4547a47bae91920/distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64", size = 609931 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/41/9307e4f5f9976bc8b7fea0b66367734e8faf3ec84bc0d412d8cfabbb66cd/distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784", size = 468850 }, +] + [[package]] name = "eval-type-backport" version = "0.2.0" @@ -247,6 +280,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/ca/086311cdfc017ec964b2436fe0c98c1f4efcb7e4c328956a22456e497655/fastjsonschema-2.20.0-py3-none-any.whl", hash = "sha256:5875f0b0fa7a0043a91e93a9b8f793bcbbba9691e7fd83dca95c28ba26d21f0a", size = 23543 }, ] +[[package]] +name = "filelock" +version = "3.16.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, +] + [[package]] name = "importlib-metadata" version = "8.5.0" @@ -658,6 +700,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 }, ] +[[package]] +name = "pyproject-api" +version = "1.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/2f/0170742908a3f89a240df567642f505657741773aaed0749f9d3aeb80ab5/pyproject_api-1.7.2.tar.gz", hash = "sha256:dc5b0e0f6e291a4f22b46e182c9c6d4915c62b1f089b8de1b73f2d06ae453593", size = 22353 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/dc/aca55d46389e719182d48c9ec09ae7f7cc5f944bdf44f90e59984d8f78f5/pyproject_api-1.7.2-py3-none-any.whl", hash = "sha256:17c025105f8d27e22ffe542fe7dff3391b3736191a28294773a1f3b9ed25282b", size = 13119 }, +] + [[package]] name = "pyright" version = "1.1.381" @@ -1108,6 +1163,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/2f/3f2f05e84a7aff787a96d5fb06821323feb370fe0baed4db6ea7b1088f32/tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7", size = 438532 }, ] +[[package]] +name = "tox" +version = "4.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/f5/bcb4b151c374d7d8c04b24084374f2f6dace0bc988e6a87e3271227e54ec/tox-4.19.0.tar.gz", hash = "sha256:66177d887f9d7ef8eaa9b58b187f7b865fa4c58650086c01336e82c9831e1867", size = 181219 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/8d/050931ea0f53c8f0e36eebcb59aa2d84fb79f290cd5ad2c671fd89a92297/tox-4.19.0-py3-none-any.whl", hash = "sha256:6e20a520db7710f6980b8ec96bde189d6b8cf41b327ec703b03e1a2a447b1aaf", size = 156853 }, +] + +[[package]] +name = "tox-uv" +version = "1.11.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "tox" }, + { name = "typing-extensions", marker = "python_full_version < '3.10'" }, + { name = "uv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/f7/dad66f115c8a1b2d4fabcea66149c56c073545ad1abeba1f9149aa32ac24/tox_uv-1.11.4.tar.gz", hash = "sha256:10a6025d751108f17d8912bf177f1804ab7f9973ab39df6e599c47f7dc849c59", size = 13683 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/24/e951e3726bc0a8a2d6e27f3924cd9430d1b2f1ff40b620ae40421487a8b0/tox_uv-1.11.4-py3-none-any.whl", hash = "sha256:e4150968466c719aa1f61f0b37b82fa43dd61f17f2c6f9ac0877572885cc6275", size = 11290 }, +] + [[package]] name = "traitlets" version = "5.14.3" @@ -1126,6 +1217,45 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, ] +[[package]] +name = "uv" +version = "0.4.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/f8/186757710d7262fd36aa0c4dfeacdb2288ecb17e3c1e40798c5d63243866/uv-0.4.12.tar.gz", hash = "sha256:0f00d15108af7b17f49d70714a31927eed27e192d5e5410822c098399d61196d", size = 1901581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/9d/404e068b70a779c1438c2e9268366f2344c84fb6ad6fa80d24a9990a1722/uv-0.4.12-py3-none-linux_armv6l.whl", hash = "sha256:31f7689c6f49b0489dc727b1e6f0f008f7db21388c3cf374577a445bd7d727b8", size = 11526223 }, + { url = "https://files.pythonhosted.org/packages/c0/e7/4face1444a283c2402d8be1c245eed0993b7bf6b1e5aacee81b66bc5e864/uv-0.4.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9aa768f4b94335a4145d74e73ff4721cb1a3e1fd1269f4bb95187a9f8d41f8e1", size = 11907813 }, + { url = "https://files.pythonhosted.org/packages/f3/24/151ccb4586c43957743b28e675894d385daff3e7783595d53e5cd9e1714c/uv-0.4.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c081b13c7789b518a2077ed0c49d33c9d855e110a2f670e4f354696245089edc", size = 11007331 }, + { url = "https://files.pythonhosted.org/packages/7b/04/ece989bccef9c86cd4a0b137a2a5d8707d024d6a832e73c2c4f20cd2117b/uv-0.4.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl", hash = "sha256:c6861b3c92da1cdc2cb18c76b0e05004413ce1cc95782a4b34b7ee002006efb8", size = 11313895 }, + { url = "https://files.pythonhosted.org/packages/9e/1a/71d63b6db35a80e3234794cee95a6283eb812ba825002092d28b27d3369f/uv-0.4.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0840d0141f54f64474c9dbd46787971859fac9deacc701091b44f1c47d066823", size = 11328920 }, + { url = "https://files.pythonhosted.org/packages/08/57/69a5b304f4c915b30127841c604df59fe39265ef277c58dafdb00b84382b/uv-0.4.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:649d2974da5d867ca0230a15aa75d6e4625c2a71eddc0abaeebe7a167038f56b", size = 11962488 }, + { url = "https://files.pythonhosted.org/packages/5a/e3/80090829f1fac0f0dfd82c81251a3b55a77797d5c36a03833d4e35409e27/uv-0.4.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6922ca516056069a6c835f0cf60053241bb3438e4ccc0356c223d4f5c0d92254", size = 12818066 }, + { url = "https://files.pythonhosted.org/packages/fa/a7/6b8753a35c647da588f5be4e831bc7af0ad84e3c2bffa50d412dc04299ce/uv-0.4.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1d2ada46563178cacfeb2ff8a3b2764381a953cee87002fad0b9181f4a35e0d", size = 12631775 }, + { url = "https://files.pythonhosted.org/packages/6e/67/8905dedc2ee06cd2d40e2faab3cd7dcaf1c29bba0440c85f5f43b57ec957/uv-0.4.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86635a9dd024d08499405c9e1c1087aa24ffbfe89eb6dde010e5a60855e661bc", size = 16036126 }, + { url = "https://files.pythonhosted.org/packages/f3/d4/42365daf1772e11278973243053ab362e9b9ba22b3a795554c4f995e0a48/uv-0.4.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67327c5997a9c4531c0e13be8545aa6568a15c99a97770ac65f6dcc5600e8a9c", size = 12277845 }, + { url = "https://files.pythonhosted.org/packages/ca/b5/c3f6115a10c162173af4737455af266243ae5a5e19b13f5ec9a0319d1f8a/uv-0.4.12-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:8a102ee30a41909634b28cb9d7d5a03af2953aa86ff941e24916093f4a74d44f", size = 11510705 }, + { url = "https://files.pythonhosted.org/packages/70/71/5675cdff0bb363c40615f21a496a2238f87254759f8ec5f0af341e17d36e/uv-0.4.12-py3-none-musllinux_1_1_armv7l.whl", hash = "sha256:e931a2add4dfec717184164a54608b99d37e0000b9c151bb020a0a2dcc6d5cc1", size = 11268523 }, + { url = "https://files.pythonhosted.org/packages/38/55/6cf828f4031046bdeda0218055cd03d545b477ac3e49ebb60d9afeb43695/uv-0.4.12-py3-none-musllinux_1_1_i686.whl", hash = "sha256:8cbfa5ed4ea167291260416d71d54ffb949b0b98bcf945190adb8c65e30492be", size = 11760792 }, + { url = "https://files.pythonhosted.org/packages/53/02/26f980f3ecccbab1ce29b2438ae69d8eee9ee7565033c2245da8bc7cd586/uv-0.4.12-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:a3c1b7b4a6e5258c0b20079beb1d22c3d306f7695eab8a3d3aea93b37db01b3a", size = 13582935 }, + { url = "https://files.pythonhosted.org/packages/82/4a/485f0ed081c713686186bfd9dc6548aefb02f1cfd764ec132cb32ca4a428/uv-0.4.12-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:dc638ff81e817a1c049c8bd51c623238dccf9bfbfb17e20878eaece6c74338bb", size = 12458025 }, + { url = "https://files.pythonhosted.org/packages/64/bb/7f14f9317e1000dc06023a8d7d35260023b5faa3420df1c6a3fd7144a7cb/uv-0.4.12-py3-none-win32.whl", hash = "sha256:0d548c090bf38fb76b6493c90bbfbad30bfc4b41365019953bffbc54d32394ed", size = 11696330 }, + { url = "https://files.pythonhosted.org/packages/c3/e2/472bf7a0acaffa3f25ea127b7671299164630d971a4234434547a5f1e23a/uv-0.4.12-py3-none-win_amd64.whl", hash = "sha256:56901b53c9bcce81305826c89378058922b405d0fbfb5c2742dda7dc5fdf891c", size = 12817590 }, +] + +[[package]] +name = "virtualenv" +version = "20.26.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/4c/66ce54c8736ff164e85117ca36b02a1e14c042a6963f85eeda82664fda4e/virtualenv-20.26.5.tar.gz", hash = "sha256:ce489cac131aa58f4b25e321d6d186171f78e6cb13fafbf32a840cee67733ff4", size = 9371932 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/1d/e1a44fdd6d30829ba21fc58b5d98a67e7aae8f4165f11d091e53aec12560/virtualenv-20.26.5-py3-none-any.whl", hash = "sha256:4f3ac17b81fba3ce3bd6f4ead2749a72da5929c01774948e243db9ba41df4ff6", size = 5999288 }, +] + [[package]] name = "wcwidth" version = "0.2.13" From 102b2fa602e9b5c6d64843bbf3bd9b2c32c2887d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 11:17:54 -0400 Subject: [PATCH 065/119] ci: Add package testing --- .github/workflows/py-test.yml | 51 +++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/py-test.yml diff --git a/.github/workflows/py-test.yml b/.github/workflows/py-test.yml new file mode 100644 index 00000000..cf1983da --- /dev/null +++ b/.github/workflows/py-test.yml @@ -0,0 +1,51 @@ +name: Test - Python + +on: + push: + paths: + - 'pkg-py/*' + - 'pyproject.toml' + - 'uv.lock' + - '.github/workflows/py-test.yml' + pull_request: + paths: + - 'pkg-py/*' + - 'pyproject.toml' + - 'uv.lock' + - '.github/workflows/py-test.yml' + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + + steps: + - uses: actions/checkout@v4 + + - name: 🚀 Install uv + uses: astral-sh/setup-uv@v2 + + - name: 🐍 Set up Python ${{ matrix.python-version }} + run: uv python install ${{ matrix.python-version }} + + - name: 📦 Install the project + run: uv sync --no-dev --extra test + + - name: 🧪 Check tests + run: make check-tests + + - name: 📝 Check types + run: make check-types + + - name: 📐 Check formatting + run: make check-format From dcf74284f3b339d9f82e697b93fcb2d925b0a4c9 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 11:19:31 -0400 Subject: [PATCH 066/119] ci(py-test): Fix workflow and make targets --- .github/workflows/py-test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/py-test.yml b/.github/workflows/py-test.yml index cf1983da..68718677 100644 --- a/.github/workflows/py-test.yml +++ b/.github/workflows/py-test.yml @@ -2,6 +2,7 @@ name: Test - Python on: push: + branches: [main] paths: - 'pkg-py/*' - 'pyproject.toml' @@ -42,10 +43,10 @@ jobs: run: uv sync --no-dev --extra test - name: 🧪 Check tests - run: make check-tests + run: make py-check-tests - name: 📝 Check types - run: make check-types + run: make py-check-types - name: 📐 Check formatting - run: make check-format + run: make py-check-format From 2bf3533a30beaeb51384207ffdf3d21f35512549 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 13:44:06 -0400 Subject: [PATCH 067/119] chore: add todo notes, other small edits --- examples/brand-posit.yml | 2 +- pkg-py/src/brand_yaml/__init__.py | 3 +++ pkg-py/src/brand_yaml/logo.py | 3 ++- pkg-py/src/brand_yaml/meta.py | 3 +++ pkg-py/src/brand_yaml/typography.py | 2 ++ pkg-py/tests/test_typography.py | 6 ++++++ 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/brand-posit.yml b/examples/brand-posit.yml index 382c8bcc..c6e1ed76 100644 --- a/examples/brand-posit.yml +++ b/examples/brand-posit.yml @@ -15,7 +15,7 @@ logo: large: posit.svg color: - with: + palette: blue: "#447099" orange: "#EE6331" gray: "#404041" diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index f2fc7942..9a86250d 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -27,8 +27,11 @@ class Brand(BaseModel): typography: BrandTypography | None = Field(None) defaults: dict[str, Any] | None = Field(None) + # TODO: fill in colors in `brand.color` + def read_brand_yaml(path: str | Path) -> Brand: + # TODO: Automatically find `_brand.yml` in project path = Path(path) with open(path, "r") as f: diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 408f5411..aee76008 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -55,7 +55,8 @@ class BrandLogo(BrandBase): @field_validator("images") @classmethod def validate_images( - cls, value: dict[str, BrandLogoImageType] | None + cls, + value: dict[str, BrandLogoImageType] | None, ) -> dict[str, BrandLogoImageType] | None: if value is None: return diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index 94903ead..1098e0e4 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -13,9 +13,12 @@ class BrandMeta(BrandBase): model_config = ConfigDict(extra="allow", str_strip_whitespace=True) + # TODO: Always return a BrandMetaName or None name: str | BrandMetaName | None = Field( None, examples=["Very Big Corporation of America"] ) + + # TODO: Always return a BrandMetaLink or None link: HttpUrl | BrandMetaLink | None = Field( None, examples=[ diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 08aea02f..81082780 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -313,6 +313,8 @@ class BrandTypographyOptionsFamily(BaseModel): class BrandTypographyOptionsLineHeight(BaseModel): + model_config = ConfigDict(populate_by_name=True) + line_height: float | None = Field(None, alias="line-height") diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index b84d695d..7dc26524 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -121,6 +121,7 @@ def test_brand_typography_monospace(): def test_brand_typography_fields_base(): base_fields = set(BrandTypographyBase.model_fields.keys()) + # TODO: Compare directly with brand-yaml spec assert base_fields == { "family", "weight", @@ -133,6 +134,7 @@ def test_brand_typography_fields_base(): def test_brand_typography_fields_headings(): headings_fields = set(BrandTypographyHeadings.model_fields.keys()) + # TODO: Compare directly with brand-yaml spec assert headings_fields == { "family", "weight", @@ -145,12 +147,14 @@ def test_brand_typography_fields_headings(): def test_brand_typography_fields_monospace(): fields = set(BrandTypographyMonospace.model_fields.keys()) + # TODO: Compare directly with brand-yaml spec assert fields == {"family", "weight", "size"} def test_brand_typography_fields_monospace_inline(): fields = set(BrandTypographyMonospaceInline.model_fields.keys()) + # TODO: Compare directly with brand-yaml spec assert fields == { "family", "weight", @@ -163,6 +167,7 @@ def test_brand_typography_fields_monospace_inline(): def test_brand_typography_fields_monospace_block(): fields = set(BrandTypographyMonospaceBlock.model_fields.keys()) + # TODO: Compare directly with brand-yaml spec assert fields == { "family", "weight", @@ -176,6 +181,7 @@ def test_brand_typography_fields_monospace_block(): def test_brand_typography_fields_link(): fields = set(BrandTypographyLink.model_fields.keys()) + # TODO: Compare directly with brand-yaml spec assert fields == { "weight", "decoration", From 7d19c00165672b6287dafc83625ba2edcc4e2bee Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 14:36:16 -0400 Subject: [PATCH 068/119] feat: Automatically find `_brand.yml` from `__file__` Or from `Path(__file__).parent`, or from a given directory --- pkg-py/src/brand_yaml/__init__.py | 47 ++++++++++++++++++- pkg-py/src/brand_yaml/_utils.py | 18 +++++++ .../tests/fixtures/find-brand-yml/_brand.yml | 2 + pkg-py/tests/fixtures/find-brand-yml/empty.py | 0 pkg-py/tests/test_brand.py | 30 ++++++++++++ 5 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 pkg-py/tests/fixtures/find-brand-yml/_brand.yml create mode 100644 pkg-py/tests/fixtures/find-brand-yml/empty.py create mode 100644 pkg-py/tests/test_brand.py diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 9a86250d..8163964a 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -6,6 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field from ruamel.yaml import YAML +from ._utils import find_project_file from .color import BrandColor from .logo import BrandLogo from .meta import BrandMeta @@ -31,9 +32,53 @@ class Brand(BaseModel): def read_brand_yaml(path: str | Path) -> Brand: - # TODO: Automatically find `_brand.yml` in project + """ + Read a brand YAML file + + Reads a brand YAML file or finds and reads a `_brand.yml` file and returns + a validated :class:`Brand` object. + + Parameters + ---------- + path + The path to the brand YAML file or a directory where `_brand.yml` is + expected to be found. Typically, you can pass `__file__` from the + calling script to find `_brand.yml` in the current directory or any of + its parent directories. + + Returns + ------- + : + A validated :class:`Brand` object with all fields populated according to + the brand YAML file. + + Raises + ------ + : + Raises a `FileNotFoundError` if no brand configuration file is found + within the given path. Raises `ValueError` or other validation errors + from [pydantic](https://docs.pydantic.dev/latest/) if the brand YAML + file is invalid. + + Examples + -------- + + ```python + from brand_yaml import read_brand_yaml + + brand = read_brand_yaml(__file__) + brand = read_brand_yaml("path/to/_brand.yml") + ``` + """ + path = Path(path) + if path.is_dir(): + path = find_project_file("_brand.yml", path) + elif path.suffix == ".py": + # allows users to simply pass `__file__` + path = find_project_file("_brand.yml", path.parent) + with open(path, "r") as f: brand_data = yaml.load(f) diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_utils.py index d92dbb99..6c3202e0 100644 --- a/pkg-py/src/brand_yaml/_utils.py +++ b/pkg-py/src/brand_yaml/_utils.py @@ -1,5 +1,7 @@ from __future__ import annotations +from pathlib import Path + from pydantic import BaseModel @@ -8,3 +10,19 @@ def __repr_args__(self): fields = [f for f in self.model_fields.keys()] values = [getattr(self, f) for f in fields] return ((f, v) for f, v in zip(fields, values) if v is not None) + + +def find_project_file(filename: str, dir_: Path) -> Path: + dir_og = dir_ + i = 0 + max_parents = 20 + + while dir_ != dir_.parent and i < max_parents: + if (dir_ / filename).exists(): + return dir_ / filename + dir_ = dir_.parent + i += 1 + + raise FileNotFoundError( + f"Could not find {filename} in {dir_og} or its parents." + ) diff --git a/pkg-py/tests/fixtures/find-brand-yml/_brand.yml b/pkg-py/tests/fixtures/find-brand-yml/_brand.yml new file mode 100644 index 00000000..10e9168a --- /dev/null +++ b/pkg-py/tests/fixtures/find-brand-yml/_brand.yml @@ -0,0 +1,2 @@ +meta: + name: Test if found automatically diff --git a/pkg-py/tests/fixtures/find-brand-yml/empty.py b/pkg-py/tests/fixtures/find-brand-yml/empty.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg-py/tests/test_brand.py b/pkg-py/tests/test_brand.py new file mode 100644 index 00000000..7dc213b2 --- /dev/null +++ b/pkg-py/tests/test_brand.py @@ -0,0 +1,30 @@ +import tempfile +from pathlib import Path + +import pytest +from brand_yaml import read_brand_yaml + + +def test_brand_yml_found_in_dir(): + path = Path(__file__).parent / "fixtures" / "find-brand-yml" / "_brand.yml" + + brand_direct = read_brand_yaml(path) + brand_found = read_brand_yaml(path.parent) + + assert brand_found == brand_direct + + +def test_brand_yml_found_from_py_file(): + path = Path(__file__).parent / "fixtures" / "find-brand-yml" / "_brand.yml" + + brand_direct = read_brand_yaml(path) + # Equivalent to passing __file__ from inside empty.py + brand_found = read_brand_yaml(path.parent / "empty.py") + + assert brand_found == brand_direct + + +def test_brand_yml_not_found_error(): + with tempfile.TemporaryDirectory() as tmpdir: + with pytest.raises(FileNotFoundError): + read_brand_yaml(tmpdir) From 6cfb8a4e6615820adf00241d46e66633bcb7d4a8 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 14:45:20 -0400 Subject: [PATCH 069/119] feat(Brand): Include hidden `source` field to track path to `brand.yml` source We'll need this to resolve paths, e.g. in logos, when validating the Brand model --- pkg-py/src/brand_yaml/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 8163964a..cb065a34 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -27,8 +27,10 @@ class Brand(BaseModel): color: BrandColor | None = Field(None) typography: BrandTypography | None = Field(None) defaults: dict[str, Any] | None = Field(None) + source: Path | None = Field(None, exclude=True, repr=False) # TODO: fill in colors in `brand.color` + # TODO: resolve paths relative to `brand.source` def read_brand_yaml(path: str | Path) -> Brand: @@ -82,6 +84,8 @@ def read_brand_yaml(path: str | Path) -> Brand: with open(path, "r") as f: brand_data = yaml.load(f) + brand_data["source"] = path + return Brand.model_validate(brand_data) From dd90630ee6b90ed211794a4ccc9de1f3d2b193f8 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 15:14:27 -0400 Subject: [PATCH 070/119] feat(BrandMeta.name): Always promote `brand.meta.name = str` to `BrandMetaName.full` --- pkg-py/src/brand_yaml/meta.py | 31 +++++++++++++++---- .../test_brand_typography_ex_fonts.json | 4 ++- .../test_brand_typography_ex_simple.json | 4 ++- pkg-py/tests/test_meta.py | 18 ++++++----- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index 1098e0e4..434604f6 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -1,6 +1,6 @@ from __future__ import annotations -from pydantic import ConfigDict, Field, HttpUrl +from pydantic import ConfigDict, Field, HttpUrl, field_validator from ._utils import BrandBase @@ -11,11 +11,15 @@ class BrandMeta(BrandBase): or project, the brand guidelines, additional links, and more. """ - model_config = ConfigDict(extra="allow", str_strip_whitespace=True) + model_config = ConfigDict( + extra="allow", + str_strip_whitespace=True, + validate_assignment=True, + ) - # TODO: Always return a BrandMetaName or None - name: str | BrandMetaName | None = Field( - None, examples=["Very Big Corporation of America"] + name: BrandMetaName | None = Field( + None, + examples=["Very Big Corporation of America"], ) # TODO: Always return a BrandMetaLink or None @@ -27,9 +31,24 @@ class BrandMeta(BrandBase): ], ) + @field_validator("name", mode="before") + @classmethod + def validate_name( + cls, + value: str | dict[str, str] | None, + ) -> dict[str, str] | None: + if isinstance(value, str): + return {"full": value} + return value + class BrandMetaName(BrandBase): - model_config = ConfigDict(extra="forbid", str_strip_whitespace=True) + model_config = ConfigDict( + extra="forbid", + str_strip_whitespace=True, + revalidate_instances="always", + validate_assignment=True, + ) full: str | None = Field(None, examples=["Very Big Corporation of America"]) short: str | None = Field(None, examples=["VBC"]) diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json index fb1f355b..a8be58f1 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json @@ -1,6 +1,8 @@ { "meta": { - "name": "examples/brand-typography-fonts.yml" + "name": { + "full": "examples/brand-typography-fonts.yml" + } }, "typography": { "base": { diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json index c1859d82..5523c38f 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json @@ -1,6 +1,8 @@ { "meta": { - "name": "examples/brand-typography-simple.yml" + "name": { + "full": "examples/brand-typography-simple.yml" + } }, "typography": { "base": { diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index 211785d4..34de4673 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -2,7 +2,7 @@ import pytest from brand_yaml import read_brand_yaml -from brand_yaml.meta import BrandMeta, BrandMetaLink +from brand_yaml.meta import BrandMeta, BrandMetaLink, BrandMetaName from utils import path_examples @@ -36,11 +36,14 @@ def test_brand_meta_empty(): assert meta_empty_name.name is None assert str(meta_empty_name.link) == "https://example.com/" - meta_empty_link = BrandMeta( - name="Very Big Corporation of America", - link=None, + meta_empty_link = BrandMeta.model_validate( + { + "name": "Very Big Corporation of America", + "link": None, + } ) - assert meta_empty_link.name == "Very Big Corporation of America" + assert isinstance(meta_empty_link.name, BrandMetaName) + assert meta_empty_link.name.full == "Very Big Corporation of America" assert meta_empty_link.link is None @@ -57,7 +60,7 @@ def test_brand_meta_yaml_full(): assert brand.meta is not None assert brand.meta.name is not None - assert not isinstance(brand.meta.name, str) + assert isinstance(brand.meta.name, BrandMetaName) assert brand.meta.name.full == "Very Big Corporation of America" assert brand.meta.name.short == "VBC" @@ -81,5 +84,6 @@ def test_brand_meta_yaml_small(): brand = read_brand_yaml(path_examples("brand-meta-small.yml")) assert brand.meta is not None - assert brand.meta.name == "Very Big Corp. of America" + assert isinstance(brand.meta.name, BrandMetaName) + assert brand.meta.name.full == "Very Big Corp. of America" assert str(brand.meta.link) == "https://very-big-corp.com/" From 26c8dcff5257f2849dc04226119d8bc24752a4cb Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 15:18:12 -0400 Subject: [PATCH 071/119] feat(BrandMeta.link): Promote single string to `brand.meta.link = {home: str}` --- pkg-py/src/brand_yaml/meta.py | 22 ++++++++++++++++++---- pkg-py/tests/test_meta.py | 6 ++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index 434604f6..0fde864f 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -22,8 +22,7 @@ class BrandMeta(BrandBase): examples=["Very Big Corporation of America"], ) - # TODO: Always return a BrandMetaLink or None - link: HttpUrl | BrandMetaLink | None = Field( + link: BrandMetaLink | None = Field( None, examples=[ "https://very-big-corp.com", @@ -33,7 +32,7 @@ class BrandMeta(BrandBase): @field_validator("name", mode="before") @classmethod - def validate_name( + def promote_str_name( cls, value: str | dict[str, str] | None, ) -> dict[str, str] | None: @@ -41,6 +40,16 @@ def validate_name( return {"full": value} return value + @field_validator("link", mode="before") + @classmethod + def promote_str_link( + cls, + value: str | dict[str, str] | None, + ) -> dict[str, str] | None: + if isinstance(value, str): + return {"home": value} + return value + class BrandMetaName(BrandBase): model_config = ConfigDict( @@ -55,7 +64,12 @@ class BrandMetaName(BrandBase): class BrandMetaLink(BrandBase): - model_config = ConfigDict(extra="allow", str_strip_whitespace=True) + model_config = ConfigDict( + extra="forbid", + str_strip_whitespace=True, + revalidate_instances="always", + validate_assignment=True, + ) home: HttpUrl | None = Field( None, diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index 34de4673..cdadbced 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -34,7 +34,8 @@ def test_brand_meta_empty(): meta_empty_name = BrandMeta(name=None, link="https://example.com") # type: ignore assert meta_empty_name.name is None - assert str(meta_empty_name.link) == "https://example.com/" + assert isinstance(meta_empty_name.link, BrandMetaLink) + assert str(meta_empty_name.link.home) == "https://example.com/" meta_empty_link = BrandMeta.model_validate( { @@ -86,4 +87,5 @@ def test_brand_meta_yaml_small(): assert brand.meta is not None assert isinstance(brand.meta.name, BrandMetaName) assert brand.meta.name.full == "Very Big Corp. of America" - assert str(brand.meta.link) == "https://very-big-corp.com/" + assert isinstance(brand.meta.link, BrandMetaLink) + assert str(brand.meta.link.home) == "https://very-big-corp.com/" From cb3cc779ea1d694271fd9c8cecea508b7fe06652 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 15:20:37 -0400 Subject: [PATCH 072/119] tests(meta): Add snapshot tests for `brand.meta` --- .../test_meta/test_brand_meta_ex_full.json | 16 ++++++++++++++++ .../test_meta/test_brand_meta_ex_small.json | 10 ++++++++++ pkg-py/tests/test_meta.py | 16 +++++++++++++--- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_full.json create mode 100644 pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_small.json diff --git a/pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_full.json b/pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_full.json new file mode 100644 index 00000000..9d566ddf --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_full.json @@ -0,0 +1,16 @@ +{ + "meta": { + "link": { + "facebook": "https://facebook.com/Very-Big-Corp", + "github": "https://github.com/Very-Big-Corp", + "home": "https://very-big-corp.com/", + "linkedin": "https://linkedin.com/company/very-big-corp", + "mastodon": "https://mastodon.social/@VeryBigCorpOfficial", + "twitter": "https://twitter.com/VeryBigCorp" + }, + "name": { + "full": "Very Big Corporation of America", + "short": "VBC" + } + } +} diff --git a/pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_small.json b/pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_small.json new file mode 100644 index 00000000..f6c7debf --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_meta/test_brand_meta_ex_small.json @@ -0,0 +1,10 @@ +{ + "meta": { + "link": { + "home": "https://very-big-corp.com/" + }, + "name": { + "full": "Very Big Corp. of America" + } + } +} diff --git a/pkg-py/tests/test_meta.py b/pkg-py/tests/test_meta.py index cdadbced..9c48c6ba 100644 --- a/pkg-py/tests/test_meta.py +++ b/pkg-py/tests/test_meta.py @@ -3,7 +3,13 @@ import pytest from brand_yaml import read_brand_yaml from brand_yaml.meta import BrandMeta, BrandMetaLink, BrandMetaName -from utils import path_examples +from syrupy.extensions.json import JSONSnapshotExtension +from utils import path_examples, pydantic_data_from_json + + +@pytest.fixture +def snapshot_json(snapshot): + return snapshot.use_extension(JSONSnapshotExtension) def test_brand_meta(): @@ -56,7 +62,7 @@ def test_brand_meta_bad_url(): ) -def test_brand_meta_yaml_full(): +def test_brand_meta_ex_full(snapshot_json): brand = read_brand_yaml(path_examples("brand-meta-full.yml")) assert brand.meta is not None @@ -80,8 +86,10 @@ def test_brand_meta_yaml_full(): assert str(brand.meta.link.twitter) == "https://twitter.com/VeryBigCorp" assert str(brand.meta.link.facebook) == "https://facebook.com/Very-Big-Corp" + assert snapshot_json == pydantic_data_from_json(brand) -def test_brand_meta_yaml_small(): + +def test_brand_meta_ex_small(snapshot_json): brand = read_brand_yaml(path_examples("brand-meta-small.yml")) assert brand.meta is not None @@ -89,3 +97,5 @@ def test_brand_meta_yaml_small(): assert brand.meta.name.full == "Very Big Corp. of America" assert isinstance(brand.meta.link, BrandMetaLink) assert str(brand.meta.link.home) == "https://very-big-corp.com/" + + assert snapshot_json == pydantic_data_from_json(brand) From d3a614db979d13cacb3a3432a67afc75ca103c6c Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 16:14:52 -0400 Subject: [PATCH 073/119] feat(brand.typography): Resolve `color`/`background-color` from `brand.color` in `brand.typography` --- examples/brand-typography-color.yml | 25 +++++++++++++ pkg-py/src/brand_yaml/__init__.py | 35 ++++++++++++++++-- pkg-py/src/brand_yaml/color.py | 23 +++++++----- pkg-py/src/brand_yaml/typography.py | 11 ++---- .../test_brand_typography_ex_color.json | 36 +++++++++++++++++++ pkg-py/tests/test_typography.py | 31 ++++++++++++++++ 6 files changed, 141 insertions(+), 20 deletions(-) create mode 100644 examples/brand-typography-color.yml create mode 100644 pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json diff --git a/examples/brand-typography-color.yml b/examples/brand-typography-color.yml new file mode 100644 index 00000000..c56f45c9 --- /dev/null +++ b/examples/brand-typography-color.yml @@ -0,0 +1,25 @@ +meta: + name: examples/brand-typography-color.yml + +color: + palette: + red: "#FF6F61" + primary: "#87CEEB" + secondary: "#50C878" + danger: red + foreground: "#1b1818" + background: "#f7f4f4" + +typography: + base: + color: foreground + headings: + color: primary + monospace-inline: + color: background + background-color: red + monospace-block: + color: foreground + background-color: background + link: + color: danger \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index cb065a34..4acf6135 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -3,7 +3,7 @@ from pathlib import Path from typing import Any -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator from ruamel.yaml import YAML from ._utils import find_project_file @@ -29,9 +29,40 @@ class Brand(BaseModel): defaults: dict[str, Any] | None = Field(None) source: Path | None = Field(None, exclude=True, repr=False) - # TODO: fill in colors in `brand.color` # TODO: resolve paths relative to `brand.source` + @model_validator(mode="after") + def resolve_typography_colors(self): + if self.typography is None or self.color is None: + return self + + color_defs = self.color._color_defs(resolved=True) + + for top_field in self.typography.model_fields.keys(): + typography_node = getattr(self.typography, top_field) + + if not isinstance(typography_node, BaseModel): + continue + + for typography_node_field in typography_node.model_fields.keys(): + if typography_node_field not in ("color", "background_color"): + continue + + value = getattr(typography_node, typography_node_field) + if value is None or not isinstance(value, str): + continue + + if value not in color_defs: + continue + + setattr( + typography_node, + typography_node_field, + color_defs[value], + ) + + return self + def read_brand_yaml(path: str | Path) -> Brand: """ diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index cbb603e5..e6e00c2b 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -103,21 +103,26 @@ def create_brand_palette(cls, value: dict[str, str] | None): return value - @model_validator(mode="after") - def resolve_palette_values(self): - # We currently resolve - _color_fields = [k for k in self.model_fields.keys() if k != "palette"] - - full_defs = deepcopy(self.palette) if self.palette is not None else {} - full_defs.update( + def _color_defs(self, resolved: bool = False) -> dict[str, str]: + defs = deepcopy(self.palette) if self.palette is not None else {} + defs.update( { k: v for k, v in self.model_dump().items() - if k in _color_fields and v is not None + if k != "palette" and v is not None } ) + + if resolved: + defs_replace_recursively(defs, defs) + return defs + else: + return defs + + @model_validator(mode="after") + def resolve_palette_values(self): defs_replace_recursively( - full_defs, + self._color_defs(resolved=False), self, name="color", exclude="palette", diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 81082780..a5c1763b 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -12,7 +12,6 @@ Field, HttpUrl, PositiveInt, - RootModel, field_validator, model_validator, ) @@ -292,20 +291,14 @@ class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): # Typography Options ----------------------------------------------------------- -class BrandNamedColor(RootModel): - root: str - - class BrandTypographyOptionsBackgroundColor(BaseModel): model_config = ConfigDict(populate_by_name=True) - background_color: BrandNamedColor | None = Field( - None, alias="background-color" - ) + background_color: str | None = Field(None, alias="background-color") class BrandTypographyOptionsColor(BaseModel): - color: BrandNamedColor | None = None + color: str | None = None class BrandTypographyOptionsFamily(BaseModel): diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json new file mode 100644 index 00000000..1c74f306 --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json @@ -0,0 +1,36 @@ +{ + "color": { + "background": "#f7f4f4", + "danger": "#FF6F61", + "foreground": "#1b1818", + "palette": { + "red": "#FF6F61" + }, + "primary": "#87CEEB", + "secondary": "#50C878" + }, + "meta": { + "name": { + "full": "examples/brand-typography-color.yml" + } + }, + "typography": { + "base": { + "color": "#1b1818" + }, + "headings": { + "color": "#87CEEB" + }, + "link": { + "color": "#FF6F61" + }, + "monospace-block": { + "background-color": "#f7f4f4", + "color": "#1b1818" + }, + "monospace-inline": { + "background-color": "#FF6F61", + "color": "#f7f4f4" + } + } +} diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 7dc26524..d769163b 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -4,6 +4,7 @@ import pytest from brand_yaml import read_brand_yaml +from brand_yaml.color import BrandColor from brand_yaml.typography import ( BrandTypography, BrandTypographyBase, @@ -324,3 +325,33 @@ def test_brand_typography_ex_fonts(snapshot_json): assert bunny_font.family == "Fira Code" assert snapshot_json == pydantic_data_from_json(brand) + + +def test_brand_typography_ex_color(snapshot_json): + brand = read_brand_yaml(path_examples("brand-typography-color.yml")) + + assert isinstance(brand.typography, BrandTypography) + assert isinstance(brand.color, BrandColor) + + t = brand.typography + color = brand.color + assert color.palette is not None + + assert isinstance(t.base, BrandTypographyBase) + assert t.base.color == color.foreground + + assert isinstance(t.headings, BrandTypographyHeadings) + assert t.headings.color == color.primary + + assert isinstance(t.monospace_inline, BrandTypographyMonospaceInline) + assert t.monospace_inline.color == color.background + assert t.monospace_inline.background_color == color.palette["red"] + + assert isinstance(t.monospace_block, BrandTypographyMonospaceBlock) + assert t.monospace_block.color == color.foreground + assert t.monospace_block.background_color == color.background + + assert isinstance(t.link, BrandTypographyLink) + assert t.link.color == color.palette["red"] + + assert snapshot_json == pydantic_data_from_json(brand) From 788f8c95d6a6c3796f8f6bf36273e1d6b240affd Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 16:15:06 -0400 Subject: [PATCH 074/119] chore(brand.logo): update TODO comment --- pkg-py/src/brand_yaml/logo.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index aee76008..095c4d49 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -46,8 +46,9 @@ class BrandLogo(BrandBase): images: dict[str, BrandLogoImageType] | None = None - # TODO: Currently we're using a string for the logo path, but we should - # update this to use a validated Path or URL in the future. + # TODO: FilePath validation + # Currently we're using a string for the logo path, but we should update + # this to use a validated Path or URL in the future. small: str | BrandLightDark[str] | None = None medium: str | BrandLightDark[str] | None = None large: str | BrandLightDark[str] | None = None From 1a745129ce536a00b024681288e1513fdc43a3db Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 18 Sep 2024 16:30:30 -0400 Subject: [PATCH 075/119] ci: sync specific python version --- .github/workflows/py-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/py-test.yml b/.github/workflows/py-test.yml index 68718677..c0e22688 100644 --- a/.github/workflows/py-test.yml +++ b/.github/workflows/py-test.yml @@ -40,7 +40,7 @@ jobs: run: uv python install ${{ matrix.python-version }} - name: 📦 Install the project - run: uv sync --no-dev --extra test + run: uv sync --python ${{ matrix.python-version }} --no-dev --extra test - name: 🧪 Check tests run: make py-check-tests From d79eea0011617307c2aeaeaec199761143a8441b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Sep 2024 11:19:31 -0400 Subject: [PATCH 076/119] chore(read_brand_yaml): error if brand yaml does not parse into a dictionary --- pkg-py/src/brand_yaml/__init__.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 4acf6135..dc3213eb 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -115,6 +115,11 @@ def read_brand_yaml(path: str | Path) -> Brand: with open(path, "r") as f: brand_data = yaml.load(f) + if not isinstance(brand_data, dict): + raise ValueError( + f"Invalid brand YAML file {str(path)!r}. Must be a dictionary." + ) + brand_data["source"] = path return Brand.model_validate(brand_data) From 0412a395e86bc331b122a6b3e9fc5118801670a5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Sep 2024 11:32:57 -0400 Subject: [PATCH 077/119] chore(brand.path): Rename from `brand.source` --- pkg-py/src/brand_yaml/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index dc3213eb..75f27e92 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -27,9 +27,9 @@ class Brand(BaseModel): color: BrandColor | None = Field(None) typography: BrandTypography | None = Field(None) defaults: dict[str, Any] | None = Field(None) - source: Path | None = Field(None, exclude=True, repr=False) + path: Path | None = Field(None, exclude=True, repr=False) - # TODO: resolve paths relative to `brand.source` + # TODO: resolve paths relative to `brand.path` @model_validator(mode="after") def resolve_typography_colors(self): @@ -120,7 +120,7 @@ def read_brand_yaml(path: str | Path) -> Brand: f"Invalid brand YAML file {str(path)!r}. Must be a dictionary." ) - brand_data["source"] = path + brand_data["path"] = path return Brand.model_validate(brand_data) From 3bfa402dea0d2ce8e263de4e4b3549581019582b Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Sep 2024 15:26:45 -0400 Subject: [PATCH 078/119] chore(brand.typography): Make supported formats a field of BrandUnsupportedFontFileFormat --- pkg-py/src/brand_yaml/typography.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index a5c1763b..4b40dbd3 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -115,10 +115,11 @@ def __init__(self, value: Any): class BrandUnsupportedFontFileFormat(ValueError): + supported = ("opentype", "truetype", "woff", "woff2") + def __init__(self, value: Any): - supported = ("opentype", "truetype", "woff", "woff2") super().__init__( - f"Unsupported font file {value!r}. Expected one of {', '.join(supported)}." + f"Unsupported font file {value!r}. Expected one of {', '.join(self.supported)}." ) @@ -182,7 +183,7 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: raise BrandUnsupportedFontFileFormat(path) fmt = FontFormats[path_ext] - if fmt not in ("opentype", "truetype", "woff", "woff2"): + if fmt not in BrandUnsupportedFontFileFormat.supported: raise BrandUnsupportedFontFileFormat(path) return fmt From 349da8abd9803734a23eb6a69de0b1fea93f8e70 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Sep 2024 15:30:01 -0400 Subject: [PATCH 079/119] chore(typography): rename constants --- pkg-py/src/brand_yaml/typography.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 4b40dbd3..d4e75e97 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -54,7 +54,7 @@ 100, 200, 300, 400, 500, 600, 700, 800, 900 ] -BrandTypographyFontWeightRoundInt = ( +font_weight_round_int = ( 100, 200, 300, @@ -67,9 +67,7 @@ ) # https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping -BrandTypographyFontWeightMap: dict[ - str, BrandTypographyFontWeightRoundIntType -] = { +font_weight_map: dict[str, BrandTypographyFontWeightRoundIntType] = { "thin": 100, "extra-light": 200, "ultra-light": 200, @@ -86,7 +84,7 @@ } # https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/src#font_formats -FontFormats = { +font_formats = { ".otc": "collection", ".ttc": "collection", ".eot": "embedded-opentype", @@ -107,7 +105,7 @@ def __init__(self, value: Any): super().__init__( f"Invalid font weight {value!r}. Expected a number divisible " + "by 100 and between 100 and 900, or one of " - + f"{', '.join(BrandTypographyFontWeightMap.keys())}." + + f"{', '.join(font_weight_map.keys())}." ) @@ -129,8 +127,8 @@ def validate_font_weight( if isinstance(value, str): if value in ("normal", "bold"): return value - if value in BrandTypographyFontWeightMap: - return BrandTypographyFontWeightMap[value] + if value in font_weight_map: + return font_weight_map[value] try: value = int(value) @@ -169,7 +167,7 @@ def validate_source(cls, value: str) -> str: if not Path(value).suffix: raise BrandUnsupportedFontFileFormat(value) - if Path(value).suffix not in FontFormats: + if Path(value).suffix not in font_formats: raise BrandUnsupportedFontFileFormat(value) return value @@ -179,10 +177,10 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: path = str(self.path) path_ext = Path(path).suffix - if path_ext not in FontFormats: + if path_ext not in font_formats: raise BrandUnsupportedFontFileFormat(path) - fmt = FontFormats[path_ext] + fmt = font_formats[path_ext] if fmt not in BrandUnsupportedFontFileFormat.supported: raise BrandUnsupportedFontFileFormat(path) @@ -192,7 +190,7 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: class BrandTypographyGoogleFontsApi(BaseModel): family: str weight: SingleOrList[BrandTypographyFontWeightSimpleType] = Field( - default=list(BrandTypographyFontWeightRoundInt) + default=list(font_weight_round_int) ) style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" From 65f0384017f678d4876f1867900022364060fee0 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Sep 2024 15:35:30 -0400 Subject: [PATCH 080/119] chore(defs_replace_recursively): Swap order of defs/items --- pkg-py/src/brand_yaml/_defs.py | 19 +++++++++++-------- pkg-py/src/brand_yaml/color.py | 6 +++--- pkg-py/src/brand_yaml/logo.py | 4 ++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 9cf0679e..f80d38b8 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -73,7 +73,7 @@ def resolve_with_values(self): return self logger.debug("validating model and resolving with_ values") - defs_replace_recursively(self.with_, self, name="with_") + defs_replace_recursively(self, defs=self.with_, name="with_") return self @@ -112,15 +112,15 @@ def defs_get( ) if isinstance(with_value, (dict, BaseModel)): - defs_replace_recursively(defs, with_value, level=level) + defs_replace_recursively(with_value, defs=defs, level=level) return with_value else: return with_value def defs_replace_recursively( - defs: Any, - items: dict | BaseModel | None = None, + items: dict | BaseModel | None, + defs: Any = None, level: int = 0, name: str | None = None, exclude: str | None = None, @@ -133,12 +133,12 @@ def defs_replace_recursively( Parameters ---------- + items + A dictionary or pydantic model in which values should be replaced. + defs A dictionary of definitions. - items - A dictionary or pydantic model to replace values in. - level The current recursion level. Used internally and for logging. @@ -149,6 +149,9 @@ def defs_replace_recursively( refer to definitions in `defs`, the are replaced with copies of the definition. """ + if defs is None: + defs = items + if level == 0: logger.debug("Checking for circular references") check_circular_references(defs, name=name) @@ -184,8 +187,8 @@ def defs_replace_recursively( # TODO: we may want to avoid recursing into child BrandWith instances logger.debug(level_indent(f"recursing into {key}", level)) defs_replace_recursively( - defs, value, + defs=defs, level=level + 1, exclude=exclude, name=name, diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index e6e00c2b..5ce6ddbf 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -99,7 +99,7 @@ def create_brand_palette(cls, value: dict[str, str] | None): # We resolve `color.palette` on load or on replacement only # TODO: Replace with class with getter/setters # Retain original values, return resolved values, and re-validate on update. - defs_replace_recursively(value, value, name="palette") + defs_replace_recursively(value, name="palette") return value @@ -114,7 +114,7 @@ def _color_defs(self, resolved: bool = False) -> dict[str, str]: ) if resolved: - defs_replace_recursively(defs, defs) + defs_replace_recursively(defs) return defs else: return defs @@ -122,8 +122,8 @@ def _color_defs(self, resolved: bool = False) -> dict[str, str]: @model_validator(mode="after") def resolve_palette_values(self): defs_replace_recursively( - self._color_defs(resolved=False), self, + defs=self._color_defs(resolved=False), name="color", exclude="palette", ) diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 095c4d49..ea595d65 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -66,7 +66,7 @@ def validate_images( # We resolve `logo.images` on load or on replacement only # TODO: Replace with class with getter/setters # Retain original values, return resolved values, and re-validate on update. - defs_replace_recursively(value, value, name="images") + defs_replace_recursively(value, name="images") return value @@ -86,8 +86,8 @@ def resolve_image_values(self): } ) defs_replace_recursively( - full_defs, self, + defs=full_defs, name="logo", exclude="images", ) From 812b415a6995e410d0cca3be81dec91cc7fd5d51 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 19 Sep 2024 22:34:38 -0400 Subject: [PATCH 081/119] feat(typography): Easy font setting, default is google font Treats `base: Open Sans` and `base.family: Open Sans` as "get from Google Fonts" For #21 --- examples/brand-typography-minimal.yml | 9 +++ pkg-py/src/brand_yaml/typography.py | 55 ++++++++++++++++++- .../test_brand_typography_ex_color.json | 1 + .../test_brand_typography_ex_minimal.json | 38 +++++++++++++ .../test_brand_typography_ex_simple.json | 14 +++++ pkg-py/tests/test_typography.py | 30 +++++++++- 6 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 examples/brand-typography-minimal.yml create mode 100644 pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json diff --git a/examples/brand-typography-minimal.yml b/examples/brand-typography-minimal.yml new file mode 100644 index 00000000..8ff78f73 --- /dev/null +++ b/examples/brand-typography-minimal.yml @@ -0,0 +1,9 @@ +meta: + name: examples/brand-typography-simple.yml +typography: + fonts: + - family: Open Sans + source: file + base: Open Sans + headings: Roboto Slab + monospace: Fira Code \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index d4e75e97..2561c942 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -146,7 +146,7 @@ class BrandTypographyFontFiles(BaseModel): source: Literal["file"] = "file" family: str - files: list[BrandTypographyFontFilesPath] + files: list[BrandTypographyFontFilesPath] = Field(default_factory=list) class BrandTypographyFontFilesPath(BaseModel): @@ -413,6 +413,59 @@ class BrandTypography(BrandBase): ) link: BrandTypographyLink | None = None + @model_validator(mode="before") + @classmethod + def simple_google_fonts(cls, data: Any): + if not isinstance(data, dict): + return data + + defined_families = set() + file_families = set() + + if ( + "fonts" in data + and isinstance(data["fonts"], list) + and len(data["fonts"]) > 0 + ): + for font in data["fonts"]: + defined_families.add(font["family"]) + if font["source"] == "file": + file_families.add(font["family"]) + else: + data["fonts"] = [] + + for field in ( + "base", + "headings", + "monospace", + "monospace_inline", + "monospace_block", + ): + if field not in data: + continue + + if not isinstance(data[field], (str, dict)): + continue + + if isinstance(data[field], str): + data[field] = {"family": data[field]} + + if "family" not in data[field]: + continue + + if data[field]["family"] in defined_families: + continue + + data["fonts"].append( + { + "family": data[field]["family"], + "source": "google", + } + ) + defined_families.add(data[field]["family"]) + + return data + @model_validator(mode="after") def forward_monospace_values(self): """ diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json index 1c74f306..ba1b90f7 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_color.json @@ -18,6 +18,7 @@ "base": { "color": "#1b1818" }, + "fonts": [], "headings": { "color": "#87CEEB" }, diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json new file mode 100644 index 00000000..1849fccb --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_minimal.json @@ -0,0 +1,38 @@ +{ + "meta": { + "name": { + "full": "examples/brand-typography-simple.yml" + } + }, + "typography": { + "base": { + "family": "Open Sans" + }, + "fonts": [ + { + "family": "Open Sans", + "source": "file" + }, + { + "family": "Roboto Slab", + "source": "google" + }, + { + "family": "Fira Code", + "source": "google" + } + ], + "headings": { + "family": "Roboto Slab" + }, + "monospace": { + "family": "Fira Code" + }, + "monospace-block": { + "family": "Fira Code" + }, + "monospace-inline": { + "family": "Fira Code" + } + } +} diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json index 5523c38f..492aa61e 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json @@ -10,6 +10,20 @@ "line-height": 1.25, "size": "1rem" }, + "fonts": [ + { + "family": "Open Sans", + "source": "google" + }, + { + "family": "Roboto Slab", + "source": "google" + }, + { + "family": "Fira Code", + "source": "google" + } + ], "headings": { "color": "primary", "family": "Roboto Slab", diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index d769163b..659074a5 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -258,7 +258,16 @@ def test_brand_typography_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-typography-simple.yml")) assert isinstance(brand.typography, BrandTypography) - assert brand.typography.fonts == [] + + assert isinstance(brand.typography.fonts, list) + assert len(brand.typography.fonts) == 3 + assert [f.family for f in brand.typography.fonts] == [ + "Open Sans", + "Roboto Slab", + "Fira Code", + ] + assert [f.source for f in brand.typography.fonts] == ["google"] * 3 + assert brand.typography.link is None assert isinstance(brand.typography.base, BrandTypographyBase) assert isinstance(brand.typography.headings, BrandTypographyHeadings) @@ -355,3 +364,22 @@ def test_brand_typography_ex_color(snapshot_json): assert t.link.color == color.palette["red"] assert snapshot_json == pydantic_data_from_json(brand) + + +def test_brand_typography_ex_minimal(snapshot_json): + brand = read_brand_yaml(path_examples("brand-typography-minimal.yml")) + + assert isinstance(brand.typography, BrandTypography) + + assert isinstance(brand.typography.fonts, list) + assert len(brand.typography.fonts) == 3 + assert brand.typography.fonts[0].source == "file" + assert brand.typography.fonts[0].files == [] + + assert isinstance(brand.typography.fonts[1], BrandTypographyFontGoogle) + assert brand.typography.fonts[1].family == "Roboto Slab" + + assert isinstance(brand.typography.fonts[2], BrandTypographyFontGoogle) + assert brand.typography.fonts[2].family == "Fira Code" + + assert snapshot_json == pydantic_data_from_json(brand) From 1fa63e413dc21eb43beee7b5f0cbbafb7181a57d Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 08:44:58 -0400 Subject: [PATCH 082/119] chore(brand.typography): Factor out common FontSource type --- pkg-py/src/brand_yaml/typography.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 2561c942..20aa19a4 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -141,11 +141,18 @@ def validate_font_weight( return value -class BrandTypographyFontFiles(BaseModel): +FontSourceType = Union[Literal["file"], Literal["google"], Literal["bunny"]] + + +class BrandTypographyFontSource(BaseModel): + source: FontSourceType = Field(frozen=True) + family: str = Field(frozen=True) + + +class BrandTypographyFontFiles(BrandTypographyFontSource): model_config = ConfigDict(extra="forbid") - source: Literal["file"] = "file" - family: str + source: Literal["file"] = Field("file", frozen=True) # type: ignore[reportIncompatibleVariableOverride] files: list[BrandTypographyFontFilesPath] = Field(default_factory=list) @@ -273,16 +280,22 @@ def _import_url_v2(self) -> str: return urljoin(str(self.url), f"css2?{params}") -class BrandTypographyFontGoogle(BrandTypographyGoogleFontsApi): +class BrandTypographyFontGoogle( + BrandTypographyFontSource, + BrandTypographyGoogleFontsApi, +): model_config = ConfigDict(extra="forbid") - source: Literal["google"] = "google" + source: Literal["google"] = Field("google", frozen=True) # type: ignore[reportIncompatibleVariableOverride] -class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): +class BrandTypographyFontBunny( + BrandTypographyFontSource, + BrandTypographyGoogleFontsApi, +): model_config = ConfigDict(extra="forbid") - source: Literal["bunny"] = "bunny" + source: Literal["bunny"] = Field("bunny", frozen=True) # type: ignore[reportIncompatibleVariableOverride] version: PositiveInt = 1 url: HttpUrl = Field("https://fonts.bunny.net/") From ac0feab5a67e90e2ef3a4a5305f2702cd156b7be Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 09:06:38 -0400 Subject: [PATCH 083/119] feat(brand.typography): Prepare CSS font declarations --- pkg-py/src/brand_yaml/typography.py | 50 +++++++++++++++---- .../tests/__snapshots__/test_typography.ambr | 31 ++++++++++++ pkg-py/tests/test_typography.py | 6 +++ 3 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 pkg-py/tests/__snapshots__/test_typography.ambr diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 20aa19a4..d745c87d 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -1,6 +1,7 @@ from __future__ import annotations import itertools +from abc import ABC, abstractmethod from pathlib import Path from typing import Annotated, Any, Literal, TypeVar, Union from urllib.parse import urlencode, urljoin @@ -144,10 +145,14 @@ def validate_font_weight( FontSourceType = Union[Literal["file"], Literal["google"], Literal["bunny"]] -class BrandTypographyFontSource(BaseModel): +class BrandTypographyFontSource(BaseModel, ABC): source: FontSourceType = Field(frozen=True) family: str = Field(frozen=True) + @abstractmethod + def css_include(self) -> str: + pass + class BrandTypographyFontFiles(BrandTypographyFontSource): model_config = ConfigDict(extra="forbid") @@ -155,6 +160,20 @@ class BrandTypographyFontFiles(BrandTypographyFontSource): source: Literal["file"] = Field("file", frozen=True) # type: ignore[reportIncompatibleVariableOverride] files: list[BrandTypographyFontFilesPath] = Field(default_factory=list) + def css_include(self) -> str: + if len(self.files) == 0: + return "" + + return "\n".join( + f"@font-face {{\n" + f" font-family: '{self.family}';\n" + f" font-weight: {font.weight};\n" + f" font-style: {font.style};\n" + f" src: {font.css_font_face_src()};\n" + f"}}" + for font in self.files + ) + class BrandTypographyFontFilesPath(BaseModel): model_config = ConfigDict(extra="forbid") @@ -193,8 +212,12 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: return fmt + def css_font_face_src(self) -> str: + # TODO: Handle `file://` vs `https://` or move to correct location + return f"url('{self.path}') format('{self.format}')" -class BrandTypographyGoogleFontsApi(BaseModel): + +class BrandTypographyGoogleFontsApi(BrandTypographyFontSource): family: str weight: SingleOrList[BrandTypographyFontWeightSimpleType] = Field( default=list(font_weight_round_int) @@ -214,6 +237,9 @@ def validate_weight( else: return validate_font_weight(value) + def css_include(self) -> str: + return f"@import url('{self.import_url()}');" + def import_url(self) -> str: if self.version == 1: return self._import_url_v1() @@ -280,19 +306,13 @@ def _import_url_v2(self) -> str: return urljoin(str(self.url), f"css2?{params}") -class BrandTypographyFontGoogle( - BrandTypographyFontSource, - BrandTypographyGoogleFontsApi, -): +class BrandTypographyFontGoogle(BrandTypographyGoogleFontsApi): model_config = ConfigDict(extra="forbid") source: Literal["google"] = Field("google", frozen=True) # type: ignore[reportIncompatibleVariableOverride] -class BrandTypographyFontBunny( - BrandTypographyFontSource, - BrandTypographyGoogleFontsApi, -): +class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): model_config = ConfigDict(extra="forbid") source: Literal["bunny"] = Field("bunny", frozen=True) # type: ignore[reportIncompatibleVariableOverride] @@ -518,3 +538,13 @@ def use_fallback(key: str): use_fallback("monospace_inline") use_fallback("monospace_block") return self + + def css_include_fonts(self) -> str: + # TODO: Download or move files into a project-relative location + + if len(self.fonts) == 0: + return "" + + includes = [font.css_include() for font in self.fonts] + + return "\n".join([i for i in includes if i]) diff --git a/pkg-py/tests/__snapshots__/test_typography.ambr b/pkg-py/tests/__snapshots__/test_typography.ambr new file mode 100644 index 00000000..d26fce0e --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_typography.ambr @@ -0,0 +1,31 @@ +# serializer version: 1 +# name: test_brand_typography_css_fonts + ''' + @font-face { + font-family: 'Open Sans'; + font-weight: bold; + font-style: normal; + src: url('Open-Sans-Bold.ttf') format('truetype'); + } + @font-face { + font-family: 'Open Sans'; + font-weight: normal; + font-style: italic; + src: url('Open-Sans-Italic.ttf') format('truetype'); + } + @font-face { + font-family: 'Closed Sans'; + font-weight: bold; + font-style: normal; + src: url('https://example.com/Closed-Sans-Bold.woff2') format('woff2'); + } + @font-face { + font-family: 'Closed Sans'; + font-weight: normal; + font-style: italic; + src: url('https://example.com/Closed-Sans-Italic.woff2') format('woff2'); + } + @import url('https://fonts.googleapis.com/css2?family=Roboto+Slab%3Aital%2Cwght%400%2C600&display=block'); + @import url('https://fonts.bunny.net/css?family=Fira+Code%3A100%2C100i%2C200%2C200i%2C300%2C300i%2C400%2C400i%2C500%2C500i%2C600%2C600i%2C700%2C700i%2C800%2C800i%2C900%2C900i&display=auto'); + ''' +# --- diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 659074a5..da25a7a7 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -383,3 +383,9 @@ def test_brand_typography_ex_minimal(snapshot_json): assert brand.typography.fonts[2].family == "Fira Code" assert snapshot_json == pydantic_data_from_json(brand) + + +def test_brand_typography_css_fonts(snapshot): + brand = read_brand_yaml(path_examples("brand-typography-fonts.yml")) + + assert snapshot == brand.typography.css_include_fonts() From 41123126aa8059fb2842c1530e06a099909029e0 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 09:27:40 -0400 Subject: [PATCH 084/119] fix(brand.typography): Assert not None in tests --- pkg-py/tests/test_typography.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index da25a7a7..7d47dac1 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -388,4 +388,5 @@ def test_brand_typography_ex_minimal(snapshot_json): def test_brand_typography_css_fonts(snapshot): brand = read_brand_yaml(path_examples("brand-typography-fonts.yml")) + assert isinstance(brand.typography, BrandTypography) assert snapshot == brand.typography.css_include_fonts() From dcbe6460fa6df14e7a6522d1d0f1c5e2f3b20704 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 09:28:14 -0400 Subject: [PATCH 085/119] fix(brand.typography): Before validators are applied to widest type --- pkg-py/src/brand_yaml/typography.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index d745c87d..3b4e2bf8 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -123,8 +123,11 @@ def __init__(self, value: Any): def validate_font_weight( - value: int | str, + value: int | str | None, ) -> BrandTypographyFontWeightSimpleType: + if value is None: + return "normal" + if isinstance(value, str): if value in ("normal", "bold"): return value @@ -184,7 +187,7 @@ class BrandTypographyFontFilesPath(BaseModel): @field_validator("weight", mode="before") @classmethod - def validate_weight(cls, value: int | str): + def validate_weight(cls, value: str | int | None): return validate_font_weight(value) @field_validator("path", mode="after") From a0458de965105cf7932a173d08797c82a69e8945 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 09:28:32 -0400 Subject: [PATCH 086/119] fix(BrandMetaLink): Allow extra links --- pkg-py/src/brand_yaml/meta.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index 0fde864f..6a412c4b 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -65,7 +65,7 @@ class BrandMetaName(BrandBase): class BrandMetaLink(BrandBase): model_config = ConfigDict( - extra="forbid", + extra="allow", str_strip_whitespace=True, revalidate_instances="always", validate_assignment=True, From 5843c5d0fd017c31eabc16207b81e5e2ac749dbc Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 11:34:10 -0400 Subject: [PATCH 087/119] fix(brand.typography): validate default URL fields for correct typing Needed so that `brand.model_dump()` doesn't complain --- pkg-py/src/brand_yaml/typography.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 3b4e2bf8..8601fd7b 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -228,7 +228,7 @@ class BrandTypographyGoogleFontsApi(BrandTypographyFontSource): style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" version: PositiveInt = 2 - url: HttpUrl = Field("https://fonts.googleapis.com/") + url: HttpUrl = Field("https://fonts.googleapis.com/", validate_default=True) @field_validator("weight", mode="before") @classmethod @@ -320,7 +320,7 @@ class BrandTypographyFontBunny(BrandTypographyGoogleFontsApi): source: Literal["bunny"] = Field("bunny", frozen=True) # type: ignore[reportIncompatibleVariableOverride] version: PositiveInt = 1 - url: HttpUrl = Field("https://fonts.bunny.net/") + url: HttpUrl = Field("https://fonts.bunny.net/", validate_default=True) # Typography Options ----------------------------------------------------------- From 4bd6271ab22ec8420cef8781a85c8b2cab569c58 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 20 Sep 2024 11:40:41 -0400 Subject: [PATCH 088/119] feat(Brand): Add `.from_yaml()` method --- pkg-py/src/brand_yaml/__init__.py | 66 +++++++++++++++++++++++++++++-- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 75f27e92..918b7bac 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any +from typing import Any, Literal, overload from pydantic import BaseModel, ConfigDict, Field, model_validator from ruamel.yaml import YAML @@ -29,6 +29,48 @@ class Brand(BaseModel): defaults: dict[str, Any] | None = Field(None) path: Path | None = Field(None, exclude=True, repr=False) + @classmethod + def from_yaml(cls, path: str | Path): + """ + Read a brand YAML file + + Reads a brand YAML file or finds and reads a `_brand.yml` file and returns + a validated :class:`Brand` object. + + Parameters + ---------- + path + The path to the brand YAML file or a directory where `_brand.yml` is + expected to be found. Typically, you can pass `__file__` from the + calling script to find `_brand.yml` in the current directory or any of + its parent directories. + + Returns + ------- + : + A validated :class:`Brand` object with all fields populated according to + the brand YAML file. + + Raises + ------ + : + Raises a `FileNotFoundError` if no brand configuration file is found + within the given path. Raises `ValueError` or other validation errors + from [pydantic](https://docs.pydantic.dev/latest/) if the brand YAML + file is invalid. + + Examples + -------- + + ```python + from brand_yaml import Brand + + brand = Brand.from_yaml(__file__) + brand = Brand.from_yaml("path/to/_brand.yml") + ``` + """ + return cls.model_validate(read_brand_yaml(path, as_data=True)) + # TODO: resolve paths relative to `brand.path` @model_validator(mode="after") @@ -64,7 +106,17 @@ def resolve_typography_colors(self): return self -def read_brand_yaml(path: str | Path) -> Brand: +@overload +def read_brand_yaml( + path: str | Path, as_data: Literal[False] = False +) -> Brand: ... + + +@overload +def read_brand_yaml(path: str | Path, as_data: Literal[True]) -> dict: ... + + +def read_brand_yaml(path: str | Path, as_data: bool = False) -> Brand | dict: """ Read a brand YAML file @@ -79,11 +131,16 @@ def read_brand_yaml(path: str | Path) -> Brand: calling script to find `_brand.yml` in the current directory or any of its parent directories. + as_data + When `True`, returns the raw brand data as a dictionary parsed from the + YAML file. When `False`, returns a validated :class:`Brand` object. + Returns ------- : A validated :class:`Brand` object with all fields populated according to - the brand YAML file. + the brand YAML file (`as_data=False`, default) or the raw brand data + as a dictionary (`as_data=True`). Raises ------ @@ -122,6 +179,9 @@ def read_brand_yaml(path: str | Path) -> Brand: brand_data["path"] = path + if as_data: + return brand_data + return Brand.model_validate(brand_data) From 0f08677a7abbd3852c670e2c2083cd46a8f55db4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 24 Sep 2024 14:05:58 -0400 Subject: [PATCH 089/119] chore: move BrandBase to `.base` --- pkg-py/src/brand_yaml/_utils.py | 9 --------- pkg-py/src/brand_yaml/base.py | 10 ++++++++++ pkg-py/src/brand_yaml/color.py | 2 +- pkg-py/src/brand_yaml/logo.py | 2 +- pkg-py/src/brand_yaml/meta.py | 2 +- pkg-py/src/brand_yaml/typography.py | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) create mode 100644 pkg-py/src/brand_yaml/base.py diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_utils.py index 6c3202e0..cf2261d4 100644 --- a/pkg-py/src/brand_yaml/_utils.py +++ b/pkg-py/src/brand_yaml/_utils.py @@ -2,15 +2,6 @@ from pathlib import Path -from pydantic import BaseModel - - -class BrandBase(BaseModel): - def __repr_args__(self): - fields = [f for f in self.model_fields.keys()] - values = [getattr(self, f) for f in fields] - return ((f, v) for f, v in zip(fields, values) if v is not None) - def find_project_file(filename: str, dir_: Path) -> Path: dir_og = dir_ diff --git a/pkg-py/src/brand_yaml/base.py b/pkg-py/src/brand_yaml/base.py new file mode 100644 index 00000000..d92dbb99 --- /dev/null +++ b/pkg-py/src/brand_yaml/base.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from pydantic import BaseModel + + +class BrandBase(BaseModel): + def __repr_args__(self): + fields = [f for f in self.model_fields.keys()] + values = [getattr(self, f) for f in fields] + return ((f, v) for f, v in zip(fields, values) if v is not None) diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index 5ce6ddbf..b67b748d 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -10,7 +10,7 @@ ) from ._defs import check_circular_references, defs_replace_recursively -from ._utils import BrandBase +from .base import BrandBase class BrandColor(BrandBase): diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index ea595d65..4388fe8b 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -10,7 +10,7 @@ check_circular_references, defs_replace_recursively, ) -from ._utils import BrandBase +from .base import BrandBase BrandLogoImageType = Union[str, BrandLightDark[str]] diff --git a/pkg-py/src/brand_yaml/meta.py b/pkg-py/src/brand_yaml/meta.py index 6a412c4b..72d5e64a 100644 --- a/pkg-py/src/brand_yaml/meta.py +++ b/pkg-py/src/brand_yaml/meta.py @@ -2,7 +2,7 @@ from pydantic import ConfigDict, Field, HttpUrl, field_validator -from ._utils import BrandBase +from .base import BrandBase class BrandMeta(BrandBase): diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 8601fd7b..755299f1 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -17,7 +17,7 @@ model_validator, ) -from ._utils import BrandBase +from .base import BrandBase # Types ------------------------------------------------------------------------ From 3dd0115f161a32ea6865e568881b61ebf6dbda16 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 24 Sep 2024 17:16:57 -0400 Subject: [PATCH 090/119] feat(typography): Validate paths relative to `_brand.yml`, allow `weight: auto` --- examples/brand-typography-fonts.yml | 5 +- .../open-sans/OpenSans-Variable-Italic.ttf | Bin 0 -> 583992 bytes .../fonts/open-sans/OpenSans-Variable.ttf | Bin 0 -> 532636 bytes pkg-py/src/brand_yaml/__init__.py | 21 +++++- pkg-py/src/brand_yaml/_path.py | 33 ++++++++++ pkg-py/src/brand_yaml/_utils.py | 60 ++++++++++++++++++ pkg-py/src/brand_yaml/typography.py | 26 ++++---- .../tests/__snapshots__/test_typography.ambr | 10 +-- .../test_brand_typography_ex_fonts.json | 5 +- pkg-py/tests/test_typography.py | 14 ++-- 10 files changed, 142 insertions(+), 32 deletions(-) create mode 100644 examples/fonts/open-sans/OpenSans-Variable-Italic.ttf create mode 100644 examples/fonts/open-sans/OpenSans-Variable.ttf create mode 100644 pkg-py/src/brand_yaml/_path.py diff --git a/examples/brand-typography-fonts.yml b/examples/brand-typography-fonts.yml index a06f1cea..de3fdf22 100644 --- a/examples/brand-typography-fonts.yml +++ b/examples/brand-typography-fonts.yml @@ -6,9 +6,8 @@ typography: - family: Open Sans source: file files: - - path: Open-Sans-Bold.ttf # TODO: FilePath validation - weight: bold - - path: Open-Sans-Italic.ttf + - path: fonts/open-sans/OpenSans-Variable.ttf + - path: fonts/open-sans/OpenSans-Variable-Italic.ttf style: italic # Online files diff --git a/examples/fonts/open-sans/OpenSans-Variable-Italic.ttf b/examples/fonts/open-sans/OpenSans-Variable-Italic.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6c2997999ce43d4ab5b377b712f2b10aca5e4bc1 GIT binary patch literal 583992 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%0D#mjc-a(__PVTE*<^lS~Uq7BJW;0 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≫}zxTVYXGYcQNSL-SNh>I(2! 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 literal 0 HcmV?d00001 diff --git a/examples/fonts/open-sans/OpenSans-Variable.ttf b/examples/fonts/open-sans/OpenSans-Variable.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9db85693b027f3b05f6d77471d215f20707127c1 GIT binary patch literal 532636 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 IF`oGQKT7FbbN~PV literal 0 HcmV?d00001 diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 918b7bac..fc1060d0 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -6,7 +6,8 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator from ruamel.yaml import YAML -from ._utils import find_project_file +from ._path import FileLocation +from ._utils import find_project_file, recurse_dicts_and_models from .color import BrandColor from .logo import BrandLogo from .meta import BrandMeta @@ -105,6 +106,22 @@ def resolve_typography_colors(self): return self + @model_validator(mode="after") + def resolve_paths(self): + path = self.path + if path is not None: + recurse_dicts_and_models( + self, + pred=lambda value: isinstance(value, FileLocation), + modify=lambda value: value._update_root_dir(path.parent), + ) + recurse_dicts_and_models( + self, + pred=lambda value: isinstance(value, FileLocation), + modify=lambda value: value._validate_path_exists(), + ) + return self + @overload def read_brand_yaml( @@ -177,7 +194,7 @@ def read_brand_yaml(path: str | Path, as_data: bool = False) -> Brand | dict: f"Invalid brand YAML file {str(path)!r}. Must be a dictionary." ) - brand_data["path"] = path + brand_data["path"] = path.absolute() if as_data: return brand_data diff --git a/pkg-py/src/brand_yaml/_path.py b/pkg-py/src/brand_yaml/_path.py new file mode 100644 index 00000000..b2ad1e6e --- /dev/null +++ b/pkg-py/src/brand_yaml/_path.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from pathlib import Path + +from pydantic import HttpUrl, RootModel + + +class FileLocation(RootModel): + root: HttpUrl | Path + _root_dir: Path + + def __init__(self, path: str | Path | HttpUrl): + super().__init__(path) + self._root_dir = Path(".").absolute() + + def __call__(self) -> Path | HttpUrl: + if isinstance(self.root, Path): + return self._root_dir / self.root + return self.root + + def _update_root_dir(self, root_dir: Path) -> bool: + self._root_dir = root_dir + return False + + def _validate_path_exists(self) -> bool: + path = self() + if not path or not isinstance(path, Path): + return False + + if not path.exists(): + raise FileNotFoundError(f"File not found: {path}") + + return False diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_utils.py index cf2261d4..088ca16e 100644 --- a/pkg-py/src/brand_yaml/_utils.py +++ b/pkg-py/src/brand_yaml/_utils.py @@ -1,6 +1,9 @@ from __future__ import annotations from pathlib import Path +from typing import Any, Callable, Dict, List + +from pydantic import BaseModel def find_project_file(filename: str, dir_: Path) -> Path: @@ -17,3 +20,60 @@ def find_project_file(filename: str, dir_: Path) -> Path: raise FileNotFoundError( f"Could not find {filename} in {dir_og} or its parents." ) + + +PredicateFuncType = Callable[[Any], bool] +ModifyFuncType = Callable[[Any], bool] + + +def recurse_dicts_and_models( + item: Dict[str, Any] | BaseModel | List[Any], + pred: PredicateFuncType, + modify: ModifyFuncType, +) -> None: + """ + Recursively traverse a nested structure of dictionaries, lists, and Pydantic + models and apply an in-place modification when a node in the nested + structure matches a predicate function. + + Parameters + ---------- + item + The nested structure to traverse. This can be a dictionary, list, or + Pydantic model. + + pred + A function that takes an item and returns a boolean indicating whether + the item should be modified. + + modify + A function that takes an item, modifies it in place, and returns a + boolean indicating whether the traversal should continue to recurse into + the item. + + Returns + ------- + : + Nothing, the function modifies the input `item` in place. + """ + + def apply(value: Any): + if pred(value): + should_recurse = modify(value) + if should_recurse: + recurse_dicts_and_models(value, pred, modify) + else: + recurse_dicts_and_models(value, pred, modify) + + if isinstance(item, BaseModel): + for field in item.model_fields.keys(): + value = getattr(item, field) + apply(value) + + elif isinstance(item, dict): + for value in item.values(): + apply(value) + + elif isinstance(item, list): + for value in item: + apply(value) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 755299f1..edf2c70c 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -17,6 +17,7 @@ model_validator, ) +from ._path import FileLocation from .base import BrandBase # Types ------------------------------------------------------------------------ @@ -48,7 +49,7 @@ ] BrandTypographyFontWeightSimpleType = Union[ - float, int, Literal["normal", "bold"] + float, int, Literal["normal", "bold", "auto"] ] BrandTypographyFontWeightRoundIntType = Literal[ @@ -126,10 +127,10 @@ def validate_font_weight( value: int | str | None, ) -> BrandTypographyFontWeightSimpleType: if value is None: - return "normal" + return "auto" if isinstance(value, str): - if value in ("normal", "bold"): + if value in ("auto", "normal", "bold"): return value if value in font_weight_map: return font_weight_map[value] @@ -181,8 +182,8 @@ def css_include(self) -> str: class BrandTypographyFontFilesPath(BaseModel): model_config = ConfigDict(extra="forbid") - path: str | HttpUrl # TODO: FilePath validation - weight: BrandTypographyFontWeightSimpleType = "normal" + path: FileLocation + weight: BrandTypographyFontWeightSimpleType = "auto" style: BrandTypographyFontStyleType = "normal" @field_validator("weight", mode="before") @@ -192,18 +193,19 @@ def validate_weight(cls, value: str | int | None): @field_validator("path", mode="after") @classmethod - def validate_source(cls, value: str) -> str: - if not Path(value).suffix: - raise BrandUnsupportedFontFileFormat(value) + def validate_source(cls, value: FileLocation) -> FileLocation: + ext = Path(str(value.root)).suffix + if not ext: + raise BrandUnsupportedFontFileFormat(value.root) - if Path(value).suffix not in font_formats: - raise BrandUnsupportedFontFileFormat(value) + if ext not in font_formats: + raise BrandUnsupportedFontFileFormat(value.root) return value @property def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: - path = str(self.path) + path = str(self.path.root) path_ext = Path(path).suffix if path_ext not in font_formats: @@ -217,7 +219,7 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: def css_font_face_src(self) -> str: # TODO: Handle `file://` vs `https://` or move to correct location - return f"url('{self.path}') format('{self.format}')" + return f"url('{self.path.root}') format('{self.format}')" class BrandTypographyGoogleFontsApi(BrandTypographyFontSource): diff --git a/pkg-py/tests/__snapshots__/test_typography.ambr b/pkg-py/tests/__snapshots__/test_typography.ambr index d26fce0e..7ceffa73 100644 --- a/pkg-py/tests/__snapshots__/test_typography.ambr +++ b/pkg-py/tests/__snapshots__/test_typography.ambr @@ -3,15 +3,15 @@ ''' @font-face { font-family: 'Open Sans'; - font-weight: bold; + font-weight: auto; font-style: normal; - src: url('Open-Sans-Bold.ttf') format('truetype'); + src: url('fonts/open-sans/OpenSans-Variable.ttf') format('truetype'); } @font-face { font-family: 'Open Sans'; - font-weight: normal; + font-weight: auto; font-style: italic; - src: url('Open-Sans-Italic.ttf') format('truetype'); + src: url('fonts/open-sans/OpenSans-Variable-Italic.ttf') format('truetype'); } @font-face { font-family: 'Closed Sans'; @@ -21,7 +21,7 @@ } @font-face { font-family: 'Closed Sans'; - font-weight: normal; + font-weight: auto; font-style: italic; src: url('https://example.com/Closed-Sans-Italic.woff2') format('woff2'); } diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json index a8be58f1..1780800e 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json @@ -15,11 +15,10 @@ "family": "Open Sans", "files": [ { - "path": "Open-Sans-Bold.ttf", - "weight": "bold" + "path": "fonts/open-sans/OpenSans-Variable.ttf" }, { - "path": "Open-Sans-Italic.ttf", + "path": "fonts/open-sans/OpenSans-Variable-Italic.ttf", "style": "italic" } ], diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index 7d47dac1..dab1e82f 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -40,7 +40,7 @@ def snapshot_json(snapshot): def test_brand_typography_font_file_format(path, fmt): font = BrandTypographyFontFilesPath(path=path) - assert font.path == path + assert str(font.path.root) == path assert font.format == fmt @@ -301,10 +301,10 @@ def test_brand_typography_ex_fonts(snapshot_json): assert local_font.family == "Open Sans" for i, font in enumerate(local_font.files): assert isinstance(font, BrandTypographyFontFilesPath) - assert str(font.path).startswith("Open-Sans") - assert str(font.path).endswith(".ttf") + assert "OpenSans" in str(font.path.root) + assert str(font.path.root).endswith(".ttf") assert font.format == "truetype" - assert font.weight == ["bold", "normal"][i] + assert font.weight == ["auto", "auto"][i] assert font.style == ["normal", "italic"][i] # Online Font Files @@ -314,10 +314,10 @@ def test_brand_typography_ex_fonts(snapshot_json): assert online_font.family == "Closed Sans" for i, font in enumerate(online_font.files): assert isinstance(font, BrandTypographyFontFilesPath) - assert str(font.path).startswith("https://") - assert str(font.path).endswith(".woff2") + assert str(font.path.root).startswith("https://") + assert str(font.path.root).endswith(".woff2") assert font.format == "woff2" - assert font.weight == ["bold", "normal"][i] + assert font.weight == ["bold", "auto"][i] assert font.style == ["normal", "italic"][i] # Google Fonts From 4b5a2f544d6a7836afce0470822b4279c7b4476f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 25 Sep 2024 16:59:40 -0400 Subject: [PATCH 091/119] feat(typography): Support variable font weights For #13 --- examples/brand-typography-fonts.yml | 2 +- pkg-py/src/brand_yaml/typography.py | 332 +++++++++++++++--- .../tests/__snapshots__/test_typography.ambr | 2 +- .../test_brand_typography_ex_fonts.json | 2 +- pkg-py/tests/test_typography.py | 116 +++--- 5 files changed, 354 insertions(+), 100 deletions(-) diff --git a/examples/brand-typography-fonts.yml b/examples/brand-typography-fonts.yml index de3fdf22..94f998f2 100644 --- a/examples/brand-typography-fonts.yml +++ b/examples/brand-typography-fonts.yml @@ -22,7 +22,7 @@ typography: # Online Font Foundries - family: Roboto Slab source: google - weight: semi-bold + weight: 600..900 style: normal display: block diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index edf2c70c..f6000f73 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -3,7 +3,17 @@ import itertools from abc import ABC, abstractmethod from pathlib import Path -from typing import Annotated, Any, Literal, TypeVar, Union +from re import split as re_split +from textwrap import indent +from typing import ( + TYPE_CHECKING, + Annotated, + Any, + Literal, + TypeVar, + Union, + overload, +) from urllib.parse import urlencode, urljoin from pydantic import ( @@ -12,8 +22,12 @@ Discriminator, Field, HttpUrl, + PlainSerializer, PositiveInt, + RootModel, + Tag, field_validator, + model_serializer, model_validator, ) @@ -26,6 +40,7 @@ T = TypeVar("T") SingleOrList = Union[T, list[T]] +SingleOrTuple = Union[T, tuple[T, ...]] BrandTypographyFontStyleType = Literal["normal", "italic"] @@ -44,12 +59,24 @@ "ultra-bold", "black", ] + +BrandTypographyFontWeightInt = Annotated[int, Field(ge=1, le=999)] + BrandTypographyFontWeightAllType = Union[ - float, int, BrandTypographyFontWeightNamedType + BrandTypographyFontWeightInt, BrandTypographyFontWeightNamedType ] BrandTypographyFontWeightSimpleType = Union[ - float, int, Literal["normal", "bold", "auto"] + BrandTypographyFontWeightInt, Literal["normal", "bold"] +] + +BrandTypographyFontWeightSimplePairedType = tuple[ + BrandTypographyFontWeightSimpleType, + BrandTypographyFontWeightSimpleType, +] + +BrandTypographyFontWeightSimpleAutoType = Union[ + BrandTypographyFontWeightInt, Literal["normal", "bold", "auto"] ] BrandTypographyFontWeightRoundIntType = Literal[ @@ -103,34 +130,50 @@ class BrandInvalidFontWeight(ValueError): - def __init__(self, value: Any): + def __init__(self, value: Any, allow_auto: bool = True): + allowed = list(font_weight_map.keys()) + if allow_auto: + allowed = ["auto", *allowed] + super().__init__( f"Invalid font weight {value!r}. Expected a number divisible " + "by 100 and between 100 and 900, or one of " - + f"{', '.join(font_weight_map.keys())}." + + f"{', '.join(allowed)}." ) -# Fonts ------------------------------------------------------------------------ - +# Font Weights ----------------------------------------------------------------- +@overload +def validate_font_weight( + value: Any, + allow_auto: Literal[True] = True, +) -> BrandTypographyFontWeightSimpleAutoType: ... -class BrandUnsupportedFontFileFormat(ValueError): - supported = ("opentype", "truetype", "woff", "woff2") - def __init__(self, value: Any): - super().__init__( - f"Unsupported font file {value!r}. Expected one of {', '.join(self.supported)}." - ) +@overload +def validate_font_weight( + value: Any, + allow_auto: Literal[False], +) -> BrandTypographyFontWeightSimpleType: ... def validate_font_weight( - value: int | str | None, -) -> BrandTypographyFontWeightSimpleType: + value: Any, + allow_auto: bool = True, +) -> ( + BrandTypographyFontWeightSimpleAutoType + | BrandTypographyFontWeightSimpleType +): if value is None: return "auto" + if not isinstance(value, (str, int, float, bool)): + raise BrandInvalidFontWeight(value, allow_auto=allow_auto) + if isinstance(value, str): - if value in ("auto", "normal", "bold"): + if allow_auto and value == "auto": + return value + if value in ("normal", "bold"): return value if value in font_weight_map: return font_weight_map[value] @@ -138,14 +181,95 @@ def validate_font_weight( try: value = int(value) except ValueError: - raise BrandInvalidFontWeight(value) + raise BrandInvalidFontWeight(value, allow_auto=allow_auto) if value < 100 or value > 900 or value % 100 != 0: - raise BrandInvalidFontWeight(value) + raise BrandInvalidFontWeight(value, allow_auto=allow_auto) return value +# Fonts (Files) ---------------------------------------------------------------- + + +class BrandUnsupportedFontFileFormat(ValueError): + supported = ("opentype", "truetype", "woff", "woff2") + + def __init__(self, value: Any): + super().__init__( + f"Unsupported font file {value!r}. Expected one of {', '.join(self.supported)}." + ) + + +class BrandTypographyFontFileWeight(RootModel): + root: ( + BrandTypographyFontWeightSimpleAutoType + | BrandTypographyFontWeightSimplePairedType + ) + + def __str__(self) -> str: + if isinstance(self.root, tuple): + vals = [ + str(font_weight_map[v]) if isinstance(v, str) else str(v) + for v in self.root + ] + return " ".join(vals) + return str(self.root) + + @model_serializer + def to_str_url(self) -> str: + if isinstance(self.root, tuple): + return f"{self.root[0]}..{self.root[1]}" + return str(self.root) + + if TYPE_CHECKING: + # https://docs.pydantic.dev/latest/concepts/serialization/#overriding-the-return-type-when-dumping-a-model + # Ensure type checkers see the correct return type + def model_dump( + self, + *, + mode: Literal["json", "python"] | str = "python", + include: Any = None, + exclude: Any = None, + context: dict[str, Any] | None = None, + by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + serialize_as_any: bool = False, + ) -> str: ... + + @field_validator("root", mode="before") + @classmethod + def validate_root_before(cls, value: Any) -> Any: + if isinstance(value, str) and ".." in value: + value = value.split("..") + return (v for v in value if v) + return value + + @field_validator("root", mode="before") + @classmethod + def validate_root( + cls, value: Any + ) -> ( + BrandTypographyFontWeightSimpleAutoType + | BrandTypographyFontWeightSimplePairedType + ): + if isinstance(value, tuple) or isinstance(value, list): + if len(value) != 2: + raise ValueError( + "Font weight ranges must have exactly 2 elements." + ) + vals = ( + validate_font_weight(value[0], allow_auto=False), + validate_font_weight(value[1], allow_auto=False), + ) + return vals + return validate_font_weight(value, allow_auto=True) + + FontSourceType = Union[Literal["file"], Literal["google"], Literal["bunny"]] @@ -169,12 +293,14 @@ def css_include(self) -> str: return "" return "\n".join( - f"@font-face {{\n" - f" font-family: '{self.family}';\n" - f" font-weight: {font.weight};\n" - f" font-style: {font.style};\n" - f" src: {font.css_font_face_src()};\n" - f"}}" + "\n".join( + [ + "@font-face {", + f" font-family: '{self.family}';", + indent(font.to_css(), 2 * " "), + "}", + ] + ) for font in self.files ) @@ -183,13 +309,23 @@ class BrandTypographyFontFilesPath(BaseModel): model_config = ConfigDict(extra="forbid") path: FileLocation - weight: BrandTypographyFontWeightSimpleType = "auto" + weight: BrandTypographyFontFileWeight = Field( + default_factory=lambda: BrandTypographyFontFileWeight(root="auto"), + validate_default=True, + ) style: BrandTypographyFontStyleType = "normal" - @field_validator("weight", mode="before") - @classmethod - def validate_weight(cls, value: str | int | None): - return validate_font_weight(value) + def to_css(self) -> str: + # TODO: Handle `file://` vs `https://` or move to correct location + weight = self.weight.to_str_url() + src = f"url('{self.path.root}') format('{self.format}')" + return "\n".join( + [ + f"font-weight: {weight};", + f"font-style: {self.style};", + f"src: {src};", + ] + ) @field_validator("path", mode="after") @classmethod @@ -217,43 +353,131 @@ def format(self) -> Literal["opentype", "truetype", "woff", "woff2"]: return fmt - def css_font_face_src(self) -> str: - # TODO: Handle `file://` vs `https://` or move to correct location - return f"url('{self.path.root}') format('{self.format}')" + +# Fonts (Google) --------------------------------------------------------------- + + +class BrandTypographyGoogleFontsWeightRange(RootModel): + model_config = ConfigDict(json_schema_mode_override="serialization") + + root: list[BrandTypographyFontWeightInt] + + def __str__(self) -> str: + return f"{self.root[0]}..{self.root[1]}" + + @model_serializer(mode="plain", when_used="always") + def to_serialized(self) -> str: + return f"{self.root[0]}..{self.root[1]}" + + def to_url_list(self) -> list[str]: + return [str(self)] + + @field_validator("root", mode="before") + @classmethod + def validate_weight(cls, value: Any) -> list[BrandTypographyFontWeightInt]: + if isinstance(value, str) and ".." in value: + start, end = re_split(r"\s*[.]{2,3}\s*", value, maxsplit=1) + value = [start, end] + + if len(value) != 2: + raise ValueError("Font weight ranges must have exactly 2 elements.") + + value = [validate_font_weight(v, allow_auto=False) for v in value] + value = [font_weight_map[v] if isinstance(v, str) else v for v in value] + return value + + if TYPE_CHECKING: + # https://docs.pydantic.dev/latest/concepts/serialization/#overriding-the-return-type-when-dumping-a-model + # Ensure type checkers see the correct return type + def model_dump( + self, + *, + mode: Literal["json", "python"] | str = "python", + include: Any = None, + exclude: Any = None, + context: dict[str, Any] | None = None, + by_alias: bool = False, + exclude_unset: bool = False, + exclude_defaults: bool = False, + exclude_none: bool = False, + round_trip: bool = False, + warnings: bool | Literal["none", "warn", "error"] = True, + serialize_as_any: bool = False, + ) -> str: ... + + +class BrandTypographyGoogleFontsWeight(RootModel): + root: ( + BrandTypographyFontWeightSimpleAutoType + | list[BrandTypographyFontWeightSimpleType] + ) + + def to_url_list(self) -> list[str]: + weights = self.root if isinstance(self.root, list) else [self.root] + vals = [ + str(font_weight_map[w]) if isinstance(w, str) else str(w) + for w in weights + ] + vals.sort() + return vals + + def to_serialized( + self, + ) -> ( + BrandTypographyFontWeightSimpleAutoType + | list[BrandTypographyFontWeightSimpleType] + ): + return self.root + + @field_validator("root", mode="before") + @classmethod + def validate_root( + cls, + value: str | int | list[str | int], + ) -> ( + BrandTypographyFontWeightSimpleAutoType + | list[BrandTypographyFontWeightSimpleType] + ): + if isinstance(value, list): + return [validate_font_weight(v, allow_auto=False) for v in value] + return validate_font_weight(value, allow_auto=True) + + +def google_font_weight_discriminator(value: Any) -> str: + if isinstance(value, str) and ".." in value: + return "range" + else: + return "weights" class BrandTypographyGoogleFontsApi(BrandTypographyFontSource): family: str - weight: SingleOrList[BrandTypographyFontWeightSimpleType] = Field( - default=list(font_weight_round_int) - ) + weight: Annotated[ + Union[ + Annotated[BrandTypographyGoogleFontsWeightRange, Tag("range")], + Annotated[BrandTypographyGoogleFontsWeight, Tag("weights")], + ], + Discriminator(google_font_weight_discriminator), + PlainSerializer( + lambda x: x.to_serialized(), + return_type=Union[str, int, list[int | str]], + ), + ] = Field(default=list(font_weight_round_int), validate_default=True) style: SingleOrList[BrandTypographyFontStyleType] = ["normal", "italic"] display: Literal["auto", "block", "swap", "fallback", "optional"] = "auto" version: PositiveInt = 2 url: HttpUrl = Field("https://fonts.googleapis.com/", validate_default=True) - @field_validator("weight", mode="before") - @classmethod - def validate_weight( - cls, value: SingleOrList[Union[int, str]] - ) -> SingleOrList[BrandTypographyFontWeightSimpleType]: - if isinstance(value, list): - return [validate_font_weight(x) for x in value] - else: - return validate_font_weight(value) - def css_include(self) -> str: - return f"@import url('{self.import_url()}');" + return f"@import url('{self.to_import_url()}');" - def import_url(self) -> str: + def to_import_url(self) -> str: if self.version == 1: return self._import_url_v1() return self._import_url_v2() def _import_url_v1(self) -> str: - weight = sorted( - self.weight if isinstance(self.weight, list) else [self.weight] - ) + weight = self.weight.to_url_list() style_str = sorted( self.style if isinstance(self.style, list) else [self.style] ) @@ -279,9 +503,7 @@ def _import_url_v1(self) -> str: return urljoin(str(self.url), f"css?{params}") def _import_url_v2(self) -> str: - weight = sorted( - self.weight if isinstance(self.weight, list) else [self.weight] - ) + weight = self.weight.to_url_list() style_str = sorted( self.style if isinstance(self.style, list) else [self.style] ) @@ -361,8 +583,8 @@ class BrandTypographyOptionsWeight(BaseModel): @field_validator("weight", mode="before") @classmethod - def validate_weight(cls, value: int | str): - return validate_font_weight(value) + def validate_weight(cls, value: Any) -> BrandTypographyFontWeightSimpleType: + return validate_font_weight(value, allow_auto=False) class BrandTypographyBase( diff --git a/pkg-py/tests/__snapshots__/test_typography.ambr b/pkg-py/tests/__snapshots__/test_typography.ambr index 7ceffa73..791a8107 100644 --- a/pkg-py/tests/__snapshots__/test_typography.ambr +++ b/pkg-py/tests/__snapshots__/test_typography.ambr @@ -25,7 +25,7 @@ font-style: italic; src: url('https://example.com/Closed-Sans-Italic.woff2') format('woff2'); } - @import url('https://fonts.googleapis.com/css2?family=Roboto+Slab%3Aital%2Cwght%400%2C600&display=block'); + @import url('https://fonts.googleapis.com/css2?family=Roboto+Slab%3Aital%2Cwght%400%2C600..900&display=block'); @import url('https://fonts.bunny.net/css?family=Fira+Code%3A100%2C100i%2C200%2C200i%2C300%2C300i%2C400%2C400i%2C500%2C500i%2C600%2C600i%2C700%2C700i%2C800%2C800i%2C900%2C900i&display=auto'); ''' # --- diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json index 1780800e..cb36355c 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json @@ -43,7 +43,7 @@ "family": "Roboto Slab", "source": "google", "style": "normal", - "weight": 600 + "weight": "600..900" }, { "family": "Fira Code", diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index dab1e82f..d3180a8f 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -11,13 +11,16 @@ BrandTypographyFontBunny, BrandTypographyFontFiles, BrandTypographyFontFilesPath, + BrandTypographyFontFileWeight, BrandTypographyFontGoogle, BrandTypographyGoogleFontsApi, + BrandTypographyGoogleFontsWeightRange, BrandTypographyHeadings, BrandTypographyLink, BrandTypographyMonospace, BrandTypographyMonospaceBlock, BrandTypographyMonospaceInline, + validate_font_weight, ) from syrupy.extensions.json import JSONSnapshotExtension from utils import path_examples, pydantic_data_from_json @@ -44,55 +47,58 @@ def test_brand_typography_font_file_format(path, fmt): assert font.format == fmt -def test_brand_typography_font_file_weight(): - args = { - "path": "my-font.otf", - } +def test_validate_font_weight(): + assert validate_font_weight(None) == "auto" + assert validate_font_weight("auto") == "auto" + assert validate_font_weight("normal") == "normal" + assert validate_font_weight("bold") == "bold" + + assert validate_font_weight("thin") == 100 + assert validate_font_weight("semi-bold") == 600 with pytest.raises(ValueError): - BrandTypographyFontFilesPath.model_validate( - {**args, "weight": "invalid"} - ) + validate_font_weight("invalid") with pytest.raises(ValueError): - BrandTypographyFontFilesPath.model_validate({**args, "weight": 999}) + validate_font_weight([100, 200]) with pytest.raises(ValueError): - BrandTypographyFontFilesPath.model_validate({**args, "weight": 150}) + # Auto is only allowed as a single value + validate_font_weight(["auto", "normal"]) + +def test_brand_typography_font_file_weight(): with pytest.raises(ValueError): - BrandTypographyFontFilesPath.model_validate({**args, "weight": 0}) + BrandTypographyFontFileWeight.model_validate("invalid") + with pytest.raises(ValueError): + BrandTypographyFontFileWeight.model_validate(999) + + with pytest.raises(ValueError): + BrandTypographyFontFileWeight.model_validate(150) + + with pytest.raises(ValueError): + BrandTypographyFontFileWeight.model_validate(0) + + assert BrandTypographyFontFileWeight.model_validate(100).root == 100 + assert BrandTypographyFontFileWeight.model_validate("thin").root == 100 + assert BrandTypographyFontFileWeight.model_validate("semi-bold").root == 600 + assert BrandTypographyFontFileWeight.model_validate("bold").root == "bold" assert ( - BrandTypographyFontFilesPath.model_validate( - {**args, "weight": 100} - ).weight - == 100 - ) - assert ( - BrandTypographyFontFilesPath.model_validate( - {**args, "weight": "thin"} - ).weight - == 100 - ) - assert ( - BrandTypographyFontFilesPath.model_validate( - {**args, "weight": "semi-bold"} - ).weight - == 600 - ) - assert ( - BrandTypographyFontFilesPath.model_validate( - {**args, "weight": "bold"} - ).weight - == "bold" + BrandTypographyFontFileWeight.model_validate("normal").root == "normal" ) - assert ( - BrandTypographyFontFilesPath.model_validate( - {**args, "weight": "normal"} - ).weight - == "normal" + assert BrandTypographyFontFileWeight.model_validate("auto").root == "auto" + + assert BrandTypographyFontFileWeight.model_validate([100, 200]).root == ( + 100, + 200, ) + thin_bold = BrandTypographyFontFileWeight.model_validate(["thin", "bold"]) + assert thin_bold.root == (100, "bold") + assert str(thin_bold) == "100 700" + + with pytest.raises(ValueError): + BrandTypographyFontFileWeight.model_validate(["thin", "auto"]) def test_brand_typography_monospace(): @@ -227,11 +233,34 @@ def test_brand_typography_font_google_import_url(): assert len(bg.fonts) == 1 assert isinstance(bg.fonts[0], BrandTypographyFontGoogle) assert ( - unquote(bg.fonts[0].import_url()) + unquote(bg.fonts[0].to_import_url()) == "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=auto" ) +def test_brand_typography_font_google_weight_range_import_url(): + bg = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "google", + "family": "Open Sans", + "weight": "400..700", + "style": ["italic", "normal"], + } + ] + } + ) + + assert len(bg.fonts) == 1 + assert isinstance(bg.fonts[0], BrandTypographyFontGoogle) + assert isinstance(bg.fonts[0].weight, BrandTypographyGoogleFontsWeightRange) + assert ( + unquote(bg.fonts[0].to_import_url()) + == "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400..700;1,400..700&display=auto" + ) + + def test_brand_typography_font_bunny_import_url(): bg = BrandTypography.model_validate( { @@ -249,7 +278,7 @@ def test_brand_typography_font_bunny_import_url(): assert len(bg.fonts) == 1 assert isinstance(bg.fonts[0], BrandTypographyFontBunny) assert ( - unquote(bg.fonts[0].import_url()) + unquote(bg.fonts[0].to_import_url()) == "https://fonts.bunny.net/css?family=Open+Sans:400,400i,700,700i&display=auto" ) @@ -304,7 +333,8 @@ def test_brand_typography_ex_fonts(snapshot_json): assert "OpenSans" in str(font.path.root) assert str(font.path.root).endswith(".ttf") assert font.format == "truetype" - assert font.weight == ["auto", "auto"][i] + assert isinstance(font.weight, BrandTypographyFontFileWeight) + assert str(font.weight) == ["auto", "auto"][i] assert font.style == ["normal", "italic"][i] # Online Font Files @@ -317,14 +347,16 @@ def test_brand_typography_ex_fonts(snapshot_json): assert str(font.path.root).startswith("https://") assert str(font.path.root).endswith(".woff2") assert font.format == "woff2" - assert font.weight == ["bold", "auto"][i] + assert str(font.weight) == ["bold", "auto"][i] assert font.style == ["normal", "italic"][i] # Google Fonts google_font = brand.typography.fonts[2] assert isinstance(google_font, BrandTypographyFontGoogle) assert google_font.family == "Roboto Slab" - assert google_font.weight == 600 + assert isinstance(google_font.weight, BrandTypographyGoogleFontsWeightRange) + assert str(google_font.weight) == "600..900" + assert google_font.weight.to_url_list() == ["600..900"] assert google_font.style == "normal" assert google_font.display == "block" From ce4db4b2f93a5c743e5f3031c6086c02f06cf2c1 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 25 Sep 2024 17:00:58 -0400 Subject: [PATCH 092/119] chore(typography): Update `validate_path()` method name --- pkg-py/src/brand_yaml/typography.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index f6000f73..31bb5b14 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -329,7 +329,7 @@ def to_css(self) -> str: @field_validator("path", mode="after") @classmethod - def validate_source(cls, value: FileLocation) -> FileLocation: + def validate_path(cls, value: FileLocation) -> FileLocation: ext = Path(str(value.root)).suffix if not ext: raise BrandUnsupportedFontFileFormat(value.root) From d7164c043676ae9c3485f59a6bf6dbde18011890 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 25 Sep 2024 17:22:41 -0400 Subject: [PATCH 093/119] fix(FileLocation): Path might already be absolute --- pkg-py/src/brand_yaml/_path.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg-py/src/brand_yaml/_path.py b/pkg-py/src/brand_yaml/_path.py index b2ad1e6e..6ff86305 100644 --- a/pkg-py/src/brand_yaml/_path.py +++ b/pkg-py/src/brand_yaml/_path.py @@ -15,6 +15,8 @@ def __init__(self, path: str | Path | HttpUrl): def __call__(self) -> Path | HttpUrl: if isinstance(self.root, Path): + if self.root.is_absolute(): + return self.root return self._root_dir / self.root return self.root From 2f93b12ae5f058b548c582651f828d9c41d30101 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 26 Sep 2024 08:53:47 -0400 Subject: [PATCH 094/119] feat: Also find `_brand.yml` in parent `brand/` or `_brand` directories --- pkg-py/src/brand_yaml/__init__.py | 6 +++--- pkg-py/src/brand_yaml/_utils.py | 13 ++++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index fc1060d0..2109077e 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -7,7 +7,7 @@ from ruamel.yaml import YAML from ._path import FileLocation -from ._utils import find_project_file, recurse_dicts_and_models +from ._utils import find_project_brand_yaml, recurse_dicts_and_models from .color import BrandColor from .logo import BrandLogo from .meta import BrandMeta @@ -181,10 +181,10 @@ def read_brand_yaml(path: str | Path, as_data: bool = False) -> Brand | dict: path = Path(path) if path.is_dir(): - path = find_project_file("_brand.yml", path) + path = find_project_brand_yaml(path) elif path.suffix == ".py": # allows users to simply pass `__file__` - path = find_project_file("_brand.yml", path.parent) + path = find_project_brand_yaml(path.parent) with open(path, "r") as f: brand_data = yaml.load(f) diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_utils.py index 088ca16e..0121cd2b 100644 --- a/pkg-py/src/brand_yaml/_utils.py +++ b/pkg-py/src/brand_yaml/_utils.py @@ -6,7 +6,11 @@ from pydantic import BaseModel -def find_project_file(filename: str, dir_: Path) -> Path: +def find_project_file( + filename: str, + dir_: Path, + subdir: tuple[str, ...] = (), +) -> Path: dir_og = dir_ i = 0 max_parents = 20 @@ -14,6 +18,9 @@ def find_project_file(filename: str, dir_: Path) -> Path: while dir_ != dir_.parent and i < max_parents: if (dir_ / filename).exists(): return dir_ / filename + for sub in subdir: + if (dir_ / sub / filename).exists(): + return dir_ / sub / filename dir_ = dir_.parent i += 1 @@ -22,6 +29,10 @@ def find_project_file(filename: str, dir_: Path) -> Path: ) +def find_project_brand_yaml(dir_: Path) -> Path: + return find_project_file("_brand.yml", dir_, ("brand", "_brand")) + + PredicateFuncType = Callable[[Any], bool] ModifyFuncType = Callable[[Any], bool] From 8e857d32f685cffb1bd7cb9e6ebe3dddbb20a80e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 26 Sep 2024 14:41:59 -0400 Subject: [PATCH 095/119] feat(FileLocation): set and validate in one (public-ish) method --- pkg-py/src/brand_yaml/__init__.py | 10 ++++------ pkg-py/src/brand_yaml/_path.py | 19 +++++++++++-------- pkg-py/src/brand_yaml/_utils.py | 4 ++-- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 2109077e..83038241 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -113,12 +113,10 @@ def resolve_paths(self): recurse_dicts_and_models( self, pred=lambda value: isinstance(value, FileLocation), - modify=lambda value: value._update_root_dir(path.parent), - ) - recurse_dicts_and_models( - self, - pred=lambda value: isinstance(value, FileLocation), - modify=lambda value: value._validate_path_exists(), + modify=lambda value: value.set_root_dir( + path.parent, + validate_path=True, + ), ) return self diff --git a/pkg-py/src/brand_yaml/_path.py b/pkg-py/src/brand_yaml/_path.py index 6ff86305..323de27d 100644 --- a/pkg-py/src/brand_yaml/_path.py +++ b/pkg-py/src/brand_yaml/_path.py @@ -7,29 +7,32 @@ class FileLocation(RootModel): root: HttpUrl | Path - _root_dir: Path + _root_dir: Path | None = None def __init__(self, path: str | Path | HttpUrl): super().__init__(path) - self._root_dir = Path(".").absolute() def __call__(self) -> Path | HttpUrl: + if self._root_dir is None: + return self.root + if isinstance(self.root, Path): if self.root.is_absolute(): return self.root return self._root_dir / self.root + return self.root - def _update_root_dir(self, root_dir: Path) -> bool: + def set_root_dir(self, root_dir: Path, validate_path: bool = False) -> None: self._root_dir = root_dir - return False - def _validate_path_exists(self) -> bool: + if validate_path: + self._validate_path_exists() + + def _validate_path_exists(self) -> None: path = self() if not path or not isinstance(path, Path): - return False + return if not path.exists(): raise FileNotFoundError(f"File not found: {path}") - - return False diff --git a/pkg-py/src/brand_yaml/_utils.py b/pkg-py/src/brand_yaml/_utils.py index 0121cd2b..cc096cdc 100644 --- a/pkg-py/src/brand_yaml/_utils.py +++ b/pkg-py/src/brand_yaml/_utils.py @@ -1,7 +1,7 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Callable, Dict, List +from typing import Any, Callable, Dict, List, Union from pydantic import BaseModel @@ -34,7 +34,7 @@ def find_project_brand_yaml(dir_: Path) -> Path: PredicateFuncType = Callable[[Any], bool] -ModifyFuncType = Callable[[Any], bool] +ModifyFuncType = Callable[[Any], Union[bool, None]] def recurse_dicts_and_models( From a4d0b7cfc6265b78fbe9d167371888e344c8eb21 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 26 Sep 2024 17:09:19 -0400 Subject: [PATCH 096/119] feat(BrandLogo): Add file validation to logo files `logo.image` is now a dict of file paths, no internal reference allowed (#22) --- examples/brand-logo-full.yml | 23 ++-- examples/brand-logo-light-dark.yml | 8 +- examples/brand-logo-simple.yml | 6 +- examples/logos/pandas/pandas.svg | 1 + examples/logos/pandas/pandas_mark.svg | 111 ++++++++++++++++++ examples/logos/pandas/pandas_mark_white.svg | 111 ++++++++++++++++++ examples/logos/pandas/pandas_secondary.svg | 1 + .../logos/pandas/pandas_secondary_white.svg | 1 + examples/logos/pandas/pandas_white.svg | 1 + pkg-py/src/brand_yaml/__init__.py | 2 - pkg-py/src/brand_yaml/_path.py | 3 + pkg-py/src/brand_yaml/logo.py | 103 ++++++++-------- .../test_logo/test_brand_logo_ex_full.json | 24 ++-- .../test_brand_logo_ex_light_dark.json | 8 +- .../test_logo/test_brand_logo_ex_simple.json | 6 +- pkg-py/tests/test_logo.py | 59 +++++++--- 16 files changed, 353 insertions(+), 115 deletions(-) create mode 100644 examples/logos/pandas/pandas.svg create mode 100644 examples/logos/pandas/pandas_mark.svg create mode 100644 examples/logos/pandas/pandas_mark_white.svg create mode 100644 examples/logos/pandas/pandas_secondary.svg create mode 100644 examples/logos/pandas/pandas_secondary_white.svg create mode 100644 examples/logos/pandas/pandas_white.svg diff --git a/examples/brand-logo-full.yml b/examples/brand-logo-full.yml index 3b24fd1c..8838eb11 100644 --- a/examples/brand-logo-full.yml +++ b/examples/brand-logo-full.yml @@ -1,14 +1,13 @@ logo: images: - primary: full-color.png - primary-svg: full-color.svg - reverse: full-color-reverse.png - black: black.png - white: white.png - icon: favicon.png - both: - light: primary - dark: reverse - small: icon - medium: both - large: primary-svg + mark: logos/pandas/pandas_mark.svg + mark-white: logos/pandas/pandas_mark_white.svg + secondary: logos/pandas/pandas_secondary.svg + secondary-white: logos/pandas/pandas_secondary_white.svg + pandas: logos/pandas/pandas.svg + pandas-white: logos/pandas/pandas_white.svg + small: mark + medium: + light: logos/pandas/pandas_secondary.svg + dark: secondary-white + large: pandas diff --git a/examples/brand-logo-light-dark.yml b/examples/brand-logo-light-dark.yml index 42bfa1a7..44762468 100644 --- a/examples/brand-logo-light-dark.yml +++ b/examples/brand-logo-light-dark.yml @@ -1,6 +1,6 @@ logo: - small: icon.png + small: logos/pandas/pandas_mark.svg medium: - light: logo-light.png - dark: logo-dark.png - large: display.svg \ No newline at end of file + light: logos/pandas/pandas_secondary.svg + dark: logos/pandas/pandas_secondary_white.svg + large: logos/pandas/pandas.svg \ No newline at end of file diff --git a/examples/brand-logo-simple.yml b/examples/brand-logo-simple.yml index e14083a5..12312d81 100644 --- a/examples/brand-logo-simple.yml +++ b/examples/brand-logo-simple.yml @@ -1,4 +1,4 @@ logo: - small: icon.png - medium: logo.png - large: display.svg \ No newline at end of file + small: logos/pandas/pandas_mark.svg + medium: logos/pandas/pandas_secondary.svg + large: logos/pandas/pandas.svg \ No newline at end of file diff --git a/examples/logos/pandas/pandas.svg b/examples/logos/pandas/pandas.svg new file mode 100644 index 00000000..a7af4e4d --- /dev/null +++ b/examples/logos/pandas/pandas.svg @@ -0,0 +1 @@ +Artboard 63 \ No newline at end of file diff --git a/examples/logos/pandas/pandas_mark.svg b/examples/logos/pandas/pandas_mark.svg new file mode 100644 index 00000000..1451f57d --- /dev/null +++ b/examples/logos/pandas/pandas_mark.svg @@ -0,0 +1,111 @@ + + + + + + image/svg+xml + + + + + + + + + Artboard 61 + + + + + + + + + diff --git a/examples/logos/pandas/pandas_mark_white.svg b/examples/logos/pandas/pandas_mark_white.svg new file mode 100644 index 00000000..ae50bf54 --- /dev/null +++ b/examples/logos/pandas/pandas_mark_white.svg @@ -0,0 +1,111 @@ + + + + + + image/svg+xml + + + + + + + + + Artboard 61 copy + + + + + + + + + diff --git a/examples/logos/pandas/pandas_secondary.svg b/examples/logos/pandas/pandas_secondary.svg new file mode 100644 index 00000000..e7440484 --- /dev/null +++ b/examples/logos/pandas/pandas_secondary.svg @@ -0,0 +1 @@ +Artboard 57 \ No newline at end of file diff --git a/examples/logos/pandas/pandas_secondary_white.svg b/examples/logos/pandas/pandas_secondary_white.svg new file mode 100644 index 00000000..86bcca57 --- /dev/null +++ b/examples/logos/pandas/pandas_secondary_white.svg @@ -0,0 +1 @@ +Artboard 57 copy \ No newline at end of file diff --git a/examples/logos/pandas/pandas_white.svg b/examples/logos/pandas/pandas_white.svg new file mode 100644 index 00000000..bc7c4165 --- /dev/null +++ b/examples/logos/pandas/pandas_white.svg @@ -0,0 +1 @@ +Artboard 63 copy 2 \ No newline at end of file diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 83038241..5b96cfd1 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -72,8 +72,6 @@ def from_yaml(cls, path: str | Path): """ return cls.model_validate(read_brand_yaml(path, as_data=True)) - # TODO: resolve paths relative to `brand.path` - @model_validator(mode="after") def resolve_typography_colors(self): if self.typography is None or self.color is None: diff --git a/pkg-py/src/brand_yaml/_path.py b/pkg-py/src/brand_yaml/_path.py index 323de27d..852b84d1 100644 --- a/pkg-py/src/brand_yaml/_path.py +++ b/pkg-py/src/brand_yaml/_path.py @@ -23,6 +23,9 @@ def __call__(self) -> Path | HttpUrl: return self.root + def __str__(self) -> str: + return str(self.root) + def set_root_dir(self, root_dir: Path, validate_path: bool = False) -> None: self._root_dir = root_dir diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 4388fe8b..1279652e 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -1,18 +1,29 @@ from __future__ import annotations -from copy import deepcopy -from typing import Union +from typing import Annotated, Any, Union -from pydantic import ConfigDict, field_validator, model_validator - -from ._defs import ( - BrandLightDark, - check_circular_references, - defs_replace_recursively, +from pydantic import ( + ConfigDict, + Discriminator, + Tag, + model_validator, ) + +from ._defs import BrandLightDark, defs_replace_recursively +from ._path import FileLocation from .base import BrandBase -BrandLogoImageType = Union[str, BrandLightDark[str]] +BrandLogoFileType = Annotated[ + Union[ + Annotated[FileLocation, Tag("file")], + Annotated[BrandLightDark[FileLocation], Tag("light-dark")], + ], + Discriminator( + lambda x: "light-dark" + if isinstance(x, (dict, BrandLightDark)) + else "file" + ), +] class BrandLogo(BrandBase): @@ -39,56 +50,34 @@ class BrandLogo(BrandBase): model_config = ConfigDict( extra="forbid", - revalidate_instances="always", - validate_assignment=True, use_attribute_docstrings=True, ) - images: dict[str, BrandLogoImageType] | None = None - - # TODO: FilePath validation - # Currently we're using a string for the logo path, but we should update - # this to use a validated Path or URL in the future. - small: str | BrandLightDark[str] | None = None - medium: str | BrandLightDark[str] | None = None - large: str | BrandLightDark[str] | None = None + images: dict[str, FileLocation] | None = None + small: BrandLogoFileType | None = None + medium: BrandLogoFileType | None = None + large: BrandLogoFileType | None = None - @field_validator("images") + @model_validator(mode="before") @classmethod - def validate_images( - cls, - value: dict[str, BrandLogoImageType] | None, - ) -> dict[str, BrandLogoImageType] | None: - if value is None: - return - - check_circular_references(value) - # We resolve `logo.images` on load or on replacement only - # TODO: Replace with class with getter/setters - # Retain original values, return resolved values, and re-validate on update. - defs_replace_recursively(value, name="images") - - return value - - @model_validator(mode="after") - def resolve_image_values(self): - if self.images is None: - return self - - _logo_fields = [k for k in self.model_fields.keys() if k != "images"] - - full_defs = deepcopy(self.images) if self.images is not None else {} - full_defs.update( - { - k: v - for k, v in self.model_dump().items() - if k in _logo_fields and v is not None - } - ) - defs_replace_recursively( - self, - defs=full_defs, - name="logo", - exclude="images", - ) - return self + def resolve_image_values(cls, data: Any): + if not isinstance(data, dict): + raise ValueError("data must be a dictionary") + + if "images" not in data: + return data + + images = data["images"] + if images is None: + return data + + if not isinstance(images, dict): + raise ValueError("images must be a dictionary of file locations") + + for key, value in images.items(): + if not isinstance(value, (str, FileLocation)): + raise ValueError(f"images[{key}] must be a file location") + + defs_replace_recursively(data, defs=images, name="logo") + + return data diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json index fcc8e195..b8c304af 100644 --- a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full.json @@ -1,22 +1,18 @@ { "logo": { "images": { - "black": "black.png", - "both": { - "dark": "full-color-reverse.png", - "light": "full-color.png" - }, - "icon": "favicon.png", - "primary": "full-color.png", - "primary-svg": "full-color.svg", - "reverse": "full-color-reverse.png", - "white": "white.png" + "mark": "logos/pandas/pandas_mark.svg", + "mark-white": "logos/pandas/pandas_mark_white.svg", + "pandas": "logos/pandas/pandas.svg", + "pandas-white": "logos/pandas/pandas_white.svg", + "secondary": "logos/pandas/pandas_secondary.svg", + "secondary-white": "logos/pandas/pandas_secondary_white.svg" }, - "large": "full-color.svg", + "large": "logos/pandas/pandas.svg", "medium": { - "dark": "full-color-reverse.png", - "light": "full-color.png" + "dark": "logos/pandas/pandas_secondary_white.svg", + "light": "logos/pandas/pandas_secondary.svg" }, - "small": "black.png" + "small": "logos/pandas/pandas_mark.svg" } } diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json index 36939a75..80eb5216 100644 --- a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_light_dark.json @@ -1,10 +1,10 @@ { "logo": { - "large": "display.svg", + "large": "logos/pandas/pandas.svg", "medium": { - "dark": "logo-dark.png", - "light": "logo-light.png" + "dark": "logos/pandas/pandas_secondary_white.svg", + "light": "logos/pandas/pandas_secondary.svg" }, - "small": "icon.png" + "small": "logos/pandas/pandas_mark.svg" } } diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json index c4a3d686..a64eba06 100644 --- a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_simple.json @@ -1,7 +1,7 @@ { "logo": { - "large": "display.svg", - "medium": "logo.png", - "small": "icon.png" + "large": "logos/pandas/pandas.svg", + "medium": "logos/pandas/pandas_secondary.svg", + "small": "logos/pandas/pandas_mark.svg" } } diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 52146ff0..0e5c8bf4 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -1,8 +1,11 @@ from __future__ import annotations +from pathlib import Path + import pytest from brand_yaml import read_brand_yaml from brand_yaml._defs import BrandLightDark +from brand_yaml._path import FileLocation from brand_yaml.logo import BrandLogo from syrupy.extensions.json import JSONSnapshotExtension from utils import path_examples, pydantic_data_from_json @@ -23,9 +26,15 @@ def test_brand_logo_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-simple.yml")) assert isinstance(brand.logo, BrandLogo) - assert brand.logo.small == "icon.png" - assert brand.logo.medium == "logo.png" - assert brand.logo.large == "display.svg" + + assert isinstance(brand.logo.small, FileLocation) + assert str(brand.logo.small) == "logos/pandas/pandas_mark.svg" + + assert isinstance(brand.logo.medium, FileLocation) + assert str(brand.logo.medium) == "logos/pandas/pandas_secondary.svg" + + assert isinstance(brand.logo.large, FileLocation) + assert str(brand.logo.large) == "logos/pandas/pandas.svg" assert snapshot_json == pydantic_data_from_json(brand) @@ -34,13 +43,19 @@ def test_brand_logo_ex_light_dark(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-light-dark.yml")) assert isinstance(brand.logo, BrandLogo) - assert brand.logo.small == "icon.png" + assert isinstance(brand.logo.small, FileLocation) + assert str(brand.logo.small) == "logos/pandas/pandas_mark.svg" assert isinstance(brand.logo.medium, BrandLightDark) - assert brand.logo.medium.light == "logo-light.png" - assert brand.logo.medium.dark == "logo-dark.png" + assert isinstance(brand.logo.medium.light, FileLocation) + assert str(brand.logo.medium.light) == "logos/pandas/pandas_secondary.svg" + assert isinstance(brand.logo.medium.dark, FileLocation) + assert ( + str(brand.logo.medium.dark) == "logos/pandas/pandas_secondary_white.svg" + ) - assert brand.logo.large == "display.svg" + assert isinstance(brand.logo.large, FileLocation) + assert str(brand.logo.large) == "logos/pandas/pandas.svg" assert snapshot_json == pydantic_data_from_json(brand) @@ -49,16 +64,28 @@ def test_brand_logo_ex_full(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-full.yml")) assert isinstance(brand.logo, BrandLogo) - assert brand.logo.small == "favicon.png" + assert isinstance(brand.logo.images, dict) + assert isinstance(brand.logo.small, FileLocation) + assert brand.logo.small == brand.logo.images["mark"] assert isinstance(brand.logo.medium, BrandLightDark) - assert brand.logo.medium.light == "full-color.png" - assert brand.logo.medium.dark == "full-color-reverse.png" - - assert brand.logo.large == "full-color.svg" - - # replace small with new value from "with" - brand.logo.small = "black" - assert brand.logo.small == "black.png" + assert isinstance(brand.logo.medium.light, FileLocation) + assert brand.logo.medium.light.root == Path( + "logos/pandas/pandas_secondary.svg" + ) + assert brand.logo.medium.dark == brand.logo.images["secondary-white"] + + assert isinstance(brand.logo.large, FileLocation) + assert brand.logo.large == brand.logo.images["pandas"] + + ## THIS IS NOT CURRENTLY SUPPORTED + ## We handle internal references in before model validation which is too + ## early for the updated field value replacement. We could revisit this if + ## we change how FileLocations are handled. + # replace small with new value from "logo.images" + # brand.logo.small = "mark-white" # type: ignore + # brand.model_rebuild() + # assert isinstance(brand.logo.small, FileLocation) + # assert brand.logo.small == brand.logo.images["mark-white"] assert snapshot_json == pydantic_data_from_json(brand) From 8ad9524f9e1d55a230f19685b12ddeeeecf4c6e9 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 1 Oct 2024 12:48:45 -0400 Subject: [PATCH 097/119] chore(read_brand_yaml): standardize path early --- pkg-py/src/brand_yaml/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 5b96cfd1..713284ab 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -174,7 +174,7 @@ def read_brand_yaml(path: str | Path, as_data: bool = False) -> Brand | dict: ``` """ - path = Path(path) + path = Path(path).absolute() if path.is_dir(): path = find_project_brand_yaml(path) @@ -190,7 +190,7 @@ def read_brand_yaml(path: str | Path, as_data: bool = False) -> Brand | dict: f"Invalid brand YAML file {str(path)!r}. Must be a dictionary." ) - brand_data["path"] = path.absolute() + brand_data["path"] = path if as_data: return brand_data From 4d80414e214f9f60d9f574b5ddfd9301559a9053 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Tue, 1 Oct 2024 13:15:39 -0400 Subject: [PATCH 098/119] feat: Throw an error if a color field refers to `color.{name}` which is undefined --- examples/brand-typography-fonts.yml | 2 ++ examples/brand-typography-simple.yml | 2 ++ pkg-py/src/brand_yaml/__init__.py | 20 ++++++++++++++---- .../test_brand_typography_ex_fonts.json | 5 ++++- .../test_brand_typography_ex_simple.json | 5 ++++- .../undefined-base-color.yml | 3 +++ .../undefined-headings-color.yml | 6 ++++++ .../undefined-monospace-background-color.yml | 3 +++ .../undefined-palette-headings-color.yml | 8 +++++++ pkg-py/tests/test_typography.py | 21 +++++++++++++++++++ 10 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 pkg-py/tests/fixtures/typography-undefined-color/undefined-base-color.yml create mode 100644 pkg-py/tests/fixtures/typography-undefined-color/undefined-headings-color.yml create mode 100644 pkg-py/tests/fixtures/typography-undefined-color/undefined-monospace-background-color.yml create mode 100644 pkg-py/tests/fixtures/typography-undefined-color/undefined-palette-headings-color.yml diff --git a/examples/brand-typography-fonts.yml b/examples/brand-typography-fonts.yml index 94f998f2..3db8c0ee 100644 --- a/examples/brand-typography-fonts.yml +++ b/examples/brand-typography-fonts.yml @@ -1,5 +1,7 @@ meta: name: examples/brand-typography-fonts.yml +color: + primary: "#f24242" typography: fonts: # Local files diff --git a/examples/brand-typography-simple.yml b/examples/brand-typography-simple.yml index 5fb735a8..0fee0094 100644 --- a/examples/brand-typography-simple.yml +++ b/examples/brand-typography-simple.yml @@ -1,5 +1,7 @@ meta: name: examples/brand-typography-simple.yml +color: + primary: blue typography: base: family: Open Sans diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 713284ab..67914dee 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -74,10 +74,13 @@ def from_yaml(cls, path: str | Path): @model_validator(mode="after") def resolve_typography_colors(self): - if self.typography is None or self.color is None: + if self.typography is None: return self - color_defs = self.color._color_defs(resolved=True) + color_defs = self.color._color_defs(resolved=True) if self.color else {} + color_names = [ + k for k in BrandColor.model_fields.keys() if k != "palette" + ] for top_field in self.typography.model_fields.keys(): typography_node = getattr(self.typography, top_field) @@ -93,8 +96,17 @@ def resolve_typography_colors(self): if value is None or not isinstance(value, str): continue - if value not in color_defs: - continue + is_defined = value in color_defs + is_theme_color = value in color_names + + if not is_defined: + if is_theme_color: + raise ValueError( + f"`typography.{top_field}.{typography_node_field}` " + f"referred to `color.{value}` which is not defined." + ) + else: + continue setattr( typography_node, diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json index cb36355c..4e117812 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_fonts.json @@ -1,4 +1,7 @@ { + "color": { + "primary": "#f24242" + }, "meta": { "name": { "full": "examples/brand-typography-fonts.yml" @@ -51,7 +54,7 @@ } ], "headings": { - "color": "primary", + "color": "#f24242", "family": "Roboto Slab", "weight": 600 }, diff --git a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json index 492aa61e..596b75ab 100644 --- a/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json +++ b/pkg-py/tests/__snapshots__/test_typography/test_brand_typography_ex_simple.json @@ -1,4 +1,7 @@ { + "color": { + "primary": "blue" + }, "meta": { "name": { "full": "examples/brand-typography-simple.yml" @@ -25,7 +28,7 @@ } ], "headings": { - "color": "primary", + "color": "blue", "family": "Roboto Slab", "weight": 600 }, diff --git a/pkg-py/tests/fixtures/typography-undefined-color/undefined-base-color.yml b/pkg-py/tests/fixtures/typography-undefined-color/undefined-base-color.yml new file mode 100644 index 00000000..238d26e6 --- /dev/null +++ b/pkg-py/tests/fixtures/typography-undefined-color/undefined-base-color.yml @@ -0,0 +1,3 @@ +typography: + base: + color: foreground diff --git a/pkg-py/tests/fixtures/typography-undefined-color/undefined-headings-color.yml b/pkg-py/tests/fixtures/typography-undefined-color/undefined-headings-color.yml new file mode 100644 index 00000000..1bcec138 --- /dev/null +++ b/pkg-py/tests/fixtures/typography-undefined-color/undefined-headings-color.yml @@ -0,0 +1,6 @@ +color: + foreground: black + background: white +typography: + headings: + color: primary # xx undefined diff --git a/pkg-py/tests/fixtures/typography-undefined-color/undefined-monospace-background-color.yml b/pkg-py/tests/fixtures/typography-undefined-color/undefined-monospace-background-color.yml new file mode 100644 index 00000000..09191f3d --- /dev/null +++ b/pkg-py/tests/fixtures/typography-undefined-color/undefined-monospace-background-color.yml @@ -0,0 +1,3 @@ +typography: + monospace: + background-color: background diff --git a/pkg-py/tests/fixtures/typography-undefined-color/undefined-palette-headings-color.yml b/pkg-py/tests/fixtures/typography-undefined-color/undefined-palette-headings-color.yml new file mode 100644 index 00000000..b109f00e --- /dev/null +++ b/pkg-py/tests/fixtures/typography-undefined-color/undefined-palette-headings-color.yml @@ -0,0 +1,8 @@ +color: + palette: + red: "#FF0000" + foreground: black + background: white +typography: + headings: + color: orange # not an error! diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index d3180a8f..d4ee9790 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from urllib.parse import unquote import pytest @@ -422,3 +423,23 @@ def test_brand_typography_css_fonts(snapshot): assert isinstance(brand.typography, BrandTypography) assert snapshot == brand.typography.css_include_fonts() + + +def test_brand_typography_undefined_colors(): + fixtures = Path(__file__).parent / "fixtures" / "typography-undefined-color" + + with pytest.raises(ValueError, match="typography.base.color"): + read_brand_yaml(fixtures / "undefined-base-color.yml") + + with pytest.raises( + ValueError, match="typography.monospace.background-color" + ): + read_brand_yaml(fixtures / "undefined-monospace-background-color.yml") + + with pytest.raises(ValueError, match="typography.headings.color"): + read_brand_yaml(fixtures / "undefined-headings-color.yml") + + brand = read_brand_yaml(fixtures / "undefined-palette-headings-color.yml") + assert isinstance(brand.typography, BrandTypography) + assert isinstance(brand.typography.headings, BrandTypographyHeadings) + assert brand.typography.headings.color == "orange" From 08cb6c906efad44ee8703338a1fa29f4a56cb4f4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 2 Oct 2024 14:26:19 -0400 Subject: [PATCH 099/119] feat(path): Simplify file location logic Files can be made absolute or relative from the top-level Brand instance --- pkg-py/src/brand_yaml/__init__.py | 42 ++++++++++--- pkg-py/src/brand_yaml/_path.py | 61 +++++++++++-------- pkg-py/src/brand_yaml/logo.py | 10 +-- pkg-py/src/brand_yaml/typography.py | 8 ++- .../tests/fixtures/path-resolution/_brand.yml | 15 +++++ pkg-py/tests/test_brand.py | 42 ++++++++++++- pkg-py/tests/test_logo.py | 16 ++--- pkg-py/tests/test_path.py | 37 +++++++++++ 8 files changed, 182 insertions(+), 49 deletions(-) create mode 100644 pkg-py/tests/fixtures/path-resolution/_brand.yml create mode 100644 pkg-py/tests/test_path.py diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 67914dee..a0871321 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -6,7 +6,7 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator from ruamel.yaml import YAML -from ._path import FileLocation +from ._path import FileLocationLocal from ._utils import find_project_brand_yaml, recurse_dicts_and_models from .color import BrandColor from .logo import BrandLogo @@ -116,17 +116,43 @@ def resolve_typography_colors(self): return self - @model_validator(mode="after") - def resolve_paths(self): + def paths_make_absolute(self): + """ + Make all paths in the brand absolute. + + Finds all fields that expect file paths in `logo` and `typography` and + converts these local file paths to local absolute file paths. In a + `_brand.yml` file, these paths are specified relative to the directory + containing the source YAML file. This method converts all local file + paths to be absolute, provided the Brand was read initially from a YAML + file via :meth:`Brand.from_yaml` or [](`brand_yaml.read_brand_yaml`). + """ + + path = self.path + if path is not None: + recurse_dicts_and_models( + self, + pred=lambda value: isinstance(value, FileLocationLocal), + modify=lambda value: value.make_absolute(path.parent), + ) + return self + + def paths_make_relative(self): + """ + Make all paths in the brand relative. + + Finds all fields that expect file paths in `logo` and `typography` and + converts absolute file paths to local file paths, relative to the source + YAML file, provided the Brand was read initially from a YAML file via + :meth:`Brand.from_yaml` or [](`brand_yaml.read_brand_yaml`). + """ + path = self.path if path is not None: recurse_dicts_and_models( self, - pred=lambda value: isinstance(value, FileLocation), - modify=lambda value: value.set_root_dir( - path.parent, - validate_path=True, - ), + pred=lambda value: isinstance(value, FileLocationLocal), + modify=lambda value: value.make_relative(path.parent), ) return self diff --git a/pkg-py/src/brand_yaml/_path.py b/pkg-py/src/brand_yaml/_path.py index 852b84d1..0ef35b03 100644 --- a/pkg-py/src/brand_yaml/_path.py +++ b/pkg-py/src/brand_yaml/_path.py @@ -1,41 +1,54 @@ from __future__ import annotations from pathlib import Path +from typing import Union -from pydantic import HttpUrl, RootModel +from pydantic import HttpUrl, RootModel, field_validator class FileLocation(RootModel): - root: HttpUrl | Path - _root_dir: Path | None = None + def __str__(self) -> str: + return str(self.root) - def __init__(self, path: str | Path | HttpUrl): - super().__init__(path) + @field_validator("root") + @classmethod + def validate_root(cls, v: Path | HttpUrl) -> Path | HttpUrl: + if isinstance(v, Path): + v = Path(v).expanduser() - def __call__(self) -> Path | HttpUrl: - if self._root_dir is None: - return self.root + vp = Path(str(v)) + if vp.suffix == "": + raise ValueError( + "Must be a path to a single file which must include an extension." + ) - if isinstance(self.root, Path): - if self.root.is_absolute(): - return self.root - return self._root_dir / self.root + return v - return self.root - def __str__(self) -> str: - return str(self.root) +class FileLocationUrl(FileLocation): + root: HttpUrl + - def set_root_dir(self, root_dir: Path, validate_path: bool = False) -> None: - self._root_dir = root_dir +class FileLocationLocal(FileLocation): + root: Path - if validate_path: - self._validate_path_exists() + def make_absolute(self, relative_to: str | Path = Path(".")): + if self.root.is_absolute(): + return + + relative_to = Path(relative_to).absolute() + self.root = relative_to / self.root - def _validate_path_exists(self) -> None: - path = self() - if not path or not isinstance(path, Path): + def make_relative(self, relative_to: str | Path = Path(".")): + if not self.root.is_absolute(): return - if not path.exists(): - raise FileNotFoundError(f"File not found: {path}") + relative_to = Path(relative_to).absolute() + self.root = self.root.relative_to(relative_to) + + def validate_path_exists(self) -> None: + if not self.root.exists(): + raise FileNotFoundError(f"File not found: {self.root}") + + +FileLocationLocalOrUrl = Union[FileLocationUrl, FileLocationLocal] diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 1279652e..bf1cbfdd 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -10,13 +10,13 @@ ) from ._defs import BrandLightDark, defs_replace_recursively -from ._path import FileLocation +from ._path import FileLocationLocalOrUrl from .base import BrandBase BrandLogoFileType = Annotated[ Union[ - Annotated[FileLocation, Tag("file")], - Annotated[BrandLightDark[FileLocation], Tag("light-dark")], + Annotated[FileLocationLocalOrUrl, Tag("file")], + Annotated[BrandLightDark[FileLocationLocalOrUrl], Tag("light-dark")], ], Discriminator( lambda x: "light-dark" @@ -53,7 +53,7 @@ class BrandLogo(BrandBase): use_attribute_docstrings=True, ) - images: dict[str, FileLocation] | None = None + images: dict[str, FileLocationLocalOrUrl] | None = None small: BrandLogoFileType | None = None medium: BrandLogoFileType | None = None large: BrandLogoFileType | None = None @@ -75,7 +75,7 @@ def resolve_image_values(cls, data: Any): raise ValueError("images must be a dictionary of file locations") for key, value in images.items(): - if not isinstance(value, (str, FileLocation)): + if not isinstance(value, (str, FileLocationLocalOrUrl)): raise ValueError(f"images[{key}] must be a file location") defs_replace_recursively(data, defs=images, name="logo") diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 31bb5b14..1745e281 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -31,7 +31,7 @@ model_validator, ) -from ._path import FileLocation +from ._path import FileLocationLocalOrUrl from .base import BrandBase # Types ------------------------------------------------------------------------ @@ -308,7 +308,7 @@ def css_include(self) -> str: class BrandTypographyFontFilesPath(BaseModel): model_config = ConfigDict(extra="forbid") - path: FileLocation + path: FileLocationLocalOrUrl weight: BrandTypographyFontFileWeight = Field( default_factory=lambda: BrandTypographyFontFileWeight(root="auto"), validate_default=True, @@ -329,7 +329,9 @@ def to_css(self) -> str: @field_validator("path", mode="after") @classmethod - def validate_path(cls, value: FileLocation) -> FileLocation: + def validate_path( + cls, value: FileLocationLocalOrUrl + ) -> FileLocationLocalOrUrl: ext = Path(str(value.root)).suffix if not ext: raise BrandUnsupportedFontFileFormat(value.root) diff --git a/pkg-py/tests/fixtures/path-resolution/_brand.yml b/pkg-py/tests/fixtures/path-resolution/_brand.yml new file mode 100644 index 00000000..11478875 --- /dev/null +++ b/pkg-py/tests/fixtures/path-resolution/_brand.yml @@ -0,0 +1,15 @@ +meta: + name: Tests that paths are resolved correctly + +logo: + images: + missing: does-not-exist.png + small: missing + +typography: + fonts: + - family: Invisible + source: file + files: + - path: Invisible.ttf + base: Invisible diff --git a/pkg-py/tests/test_brand.py b/pkg-py/tests/test_brand.py index 7dc213b2..fe198378 100644 --- a/pkg-py/tests/test_brand.py +++ b/pkg-py/tests/test_brand.py @@ -3,10 +3,15 @@ import pytest from brand_yaml import read_brand_yaml +from brand_yaml._path import FileLocationLocal +from brand_yaml.logo import BrandLogo +from brand_yaml.typography import BrandTypography, BrandTypographyFontFiles + +path_fixtures = Path(__file__).parent / "fixtures" def test_brand_yml_found_in_dir(): - path = Path(__file__).parent / "fixtures" / "find-brand-yml" / "_brand.yml" + path = path_fixtures / "find-brand-yml" / "_brand.yml" brand_direct = read_brand_yaml(path) brand_found = read_brand_yaml(path.parent) @@ -28,3 +33,38 @@ def test_brand_yml_not_found_error(): with tempfile.TemporaryDirectory() as tmpdir: with pytest.raises(FileNotFoundError): read_brand_yaml(tmpdir) + + +def test_brand_yml_paths(): + path = path_fixtures / "path-resolution" + + # This doesn't error, even though it points to missing files + brand = read_brand_yaml(path) + + assert isinstance(brand.logo, BrandLogo) + + assert isinstance(brand.typography, BrandTypography) + assert isinstance(brand.typography.fonts, list) + assert isinstance(brand.typography.fonts[0], BrandTypographyFontFiles) + + # Paths are all relative initially + assert str(brand.logo.small) == "does-not-exist.png" + assert str(brand.typography.fonts[0].files[0].path) == "Invisible.ttf" + + # but can be made absolute with a method call + brand.paths_make_absolute() + assert isinstance(brand.logo.small, FileLocationLocal) + assert brand.logo.small.root == path.absolute() / "does-not-exist.png" + assert isinstance( + brand.typography.fonts[0].files[0].path, + FileLocationLocal, + ) + assert ( + brand.typography.fonts[0].files[0].path.root + == path.absolute() / "Invisible.ttf" + ) + + # which can be reversed again back to relative paths (possibly destructive) + brand.paths_make_relative() + assert str(brand.logo.small) == "does-not-exist.png" + assert str(brand.typography.fonts[0].files[0].path) == "Invisible.ttf" diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 0e5c8bf4..5a7c8407 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -5,7 +5,7 @@ import pytest from brand_yaml import read_brand_yaml from brand_yaml._defs import BrandLightDark -from brand_yaml._path import FileLocation +from brand_yaml._path import FileLocation, FileLocationLocal from brand_yaml.logo import BrandLogo from syrupy.extensions.json import JSONSnapshotExtension from utils import path_examples, pydantic_data_from_json @@ -43,18 +43,18 @@ def test_brand_logo_ex_light_dark(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-light-dark.yml")) assert isinstance(brand.logo, BrandLogo) - assert isinstance(brand.logo.small, FileLocation) + assert isinstance(brand.logo.small, FileLocationLocal) assert str(brand.logo.small) == "logos/pandas/pandas_mark.svg" assert isinstance(brand.logo.medium, BrandLightDark) - assert isinstance(brand.logo.medium.light, FileLocation) + assert isinstance(brand.logo.medium.light, FileLocationLocal) assert str(brand.logo.medium.light) == "logos/pandas/pandas_secondary.svg" - assert isinstance(brand.logo.medium.dark, FileLocation) + assert isinstance(brand.logo.medium.dark, FileLocationLocal) assert ( str(brand.logo.medium.dark) == "logos/pandas/pandas_secondary_white.svg" ) - assert isinstance(brand.logo.large, FileLocation) + assert isinstance(brand.logo.large, FileLocationLocal) assert str(brand.logo.large) == "logos/pandas/pandas.svg" assert snapshot_json == pydantic_data_from_json(brand) @@ -65,17 +65,17 @@ def test_brand_logo_ex_full(snapshot_json): assert isinstance(brand.logo, BrandLogo) assert isinstance(brand.logo.images, dict) - assert isinstance(brand.logo.small, FileLocation) + assert isinstance(brand.logo.small, FileLocationLocal) assert brand.logo.small == brand.logo.images["mark"] assert isinstance(brand.logo.medium, BrandLightDark) - assert isinstance(brand.logo.medium.light, FileLocation) + assert isinstance(brand.logo.medium.light, FileLocationLocal) assert brand.logo.medium.light.root == Path( "logos/pandas/pandas_secondary.svg" ) assert brand.logo.medium.dark == brand.logo.images["secondary-white"] - assert isinstance(brand.logo.large, FileLocation) + assert isinstance(brand.logo.large, FileLocationLocal) assert brand.logo.large == brand.logo.images["pandas"] ## THIS IS NOT CURRENTLY SUPPORTED diff --git a/pkg-py/tests/test_path.py b/pkg-py/tests/test_path.py new file mode 100644 index 00000000..ccad4151 --- /dev/null +++ b/pkg-py/tests/test_path.py @@ -0,0 +1,37 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +from brand_yaml._path import FileLocation, FileLocationLocal, FileLocationUrl + + +def test_file_requires_extension(): + with pytest.raises(ValueError): + FileLocationLocal.model_validate("fancy-logo") + + with pytest.raises(ValueError): + FileLocationUrl.model_validate("https://example.com/my-font") + + local = FileLocationLocal.model_validate("fancy-logo.png") + assert isinstance(local, FileLocation) + assert isinstance(local, FileLocationLocal) + assert str(local) == "fancy-logo.png" + + url = FileLocationUrl.model_validate("https://example.com/my-font.ttf") + assert isinstance(url, FileLocationUrl) + assert isinstance(url, FileLocation) + assert str(url) == "https://example.com/my-font.ttf" + + +def test_local_file_path_resolution(): + local = FileLocationLocal.model_validate("fancy-logo.png") + assert isinstance(local, FileLocation) + assert isinstance(local, FileLocationLocal) + assert str(local) == "fancy-logo.png" + + local.make_absolute(Path(__file__).parent) + assert str(local) == str(Path(__file__).parent / "fancy-logo.png") + + with pytest.raises(FileNotFoundError): + local.validate_path_exists() From 82100a978a848f9ca1cf43833f7c3724a0d31138 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 2 Oct 2024 14:28:12 -0400 Subject: [PATCH 100/119] chore: Move `FileLocation` to `.file` --- pkg-py/src/brand_yaml/__init__.py | 8 +++++--- pkg-py/src/brand_yaml/{_path.py => file.py} | 0 pkg-py/src/brand_yaml/logo.py | 2 +- pkg-py/src/brand_yaml/typography.py | 2 +- pkg-py/tests/test_brand.py | 2 +- pkg-py/tests/test_logo.py | 2 +- pkg-py/tests/test_path.py | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) rename pkg-py/src/brand_yaml/{_path.py => file.py} (100%) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index a0871321..250f8aa2 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -6,9 +6,9 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator from ruamel.yaml import YAML -from ._path import FileLocationLocal from ._utils import find_project_brand_yaml, recurse_dicts_and_models from .color import BrandColor +from .file import FileLocationLocal from .logo import BrandLogo from .meta import BrandMeta from .typography import BrandTypography @@ -125,7 +125,8 @@ def paths_make_absolute(self): `_brand.yml` file, these paths are specified relative to the directory containing the source YAML file. This method converts all local file paths to be absolute, provided the Brand was read initially from a YAML - file via :meth:`Brand.from_yaml` or [](`brand_yaml.read_brand_yaml`). + file via :meth:`brand_yaml.Brand.from_yaml` or + :func:`brand_yaml.read_brand_yaml`. """ path = self.path @@ -144,7 +145,8 @@ def paths_make_relative(self): Finds all fields that expect file paths in `logo` and `typography` and converts absolute file paths to local file paths, relative to the source YAML file, provided the Brand was read initially from a YAML file via - :meth:`Brand.from_yaml` or [](`brand_yaml.read_brand_yaml`). + :meth:`brand_yaml.Brand.from_yaml` or + :func:`brand_yaml.read_brand_yaml`. """ path = self.path diff --git a/pkg-py/src/brand_yaml/_path.py b/pkg-py/src/brand_yaml/file.py similarity index 100% rename from pkg-py/src/brand_yaml/_path.py rename to pkg-py/src/brand_yaml/file.py diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index bf1cbfdd..646698bb 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -10,8 +10,8 @@ ) from ._defs import BrandLightDark, defs_replace_recursively -from ._path import FileLocationLocalOrUrl from .base import BrandBase +from .file import FileLocationLocalOrUrl BrandLogoFileType = Annotated[ Union[ diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 1745e281..65d757dd 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -31,8 +31,8 @@ model_validator, ) -from ._path import FileLocationLocalOrUrl from .base import BrandBase +from .file import FileLocationLocalOrUrl # Types ------------------------------------------------------------------------ diff --git a/pkg-py/tests/test_brand.py b/pkg-py/tests/test_brand.py index fe198378..fa27060b 100644 --- a/pkg-py/tests/test_brand.py +++ b/pkg-py/tests/test_brand.py @@ -3,7 +3,7 @@ import pytest from brand_yaml import read_brand_yaml -from brand_yaml._path import FileLocationLocal +from brand_yaml.file import FileLocationLocal from brand_yaml.logo import BrandLogo from brand_yaml.typography import BrandTypography, BrandTypographyFontFiles diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 5a7c8407..1a650274 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -5,7 +5,7 @@ import pytest from brand_yaml import read_brand_yaml from brand_yaml._defs import BrandLightDark -from brand_yaml._path import FileLocation, FileLocationLocal +from brand_yaml.file import FileLocation, FileLocationLocal from brand_yaml.logo import BrandLogo from syrupy.extensions.json import JSONSnapshotExtension from utils import path_examples, pydantic_data_from_json diff --git a/pkg-py/tests/test_path.py b/pkg-py/tests/test_path.py index ccad4151..13a46d73 100644 --- a/pkg-py/tests/test_path.py +++ b/pkg-py/tests/test_path.py @@ -3,7 +3,7 @@ from pathlib import Path import pytest -from brand_yaml._path import FileLocation, FileLocationLocal, FileLocationUrl +from brand_yaml.file import FileLocation, FileLocationLocal, FileLocationUrl def test_file_requires_extension(): From c9c1e5f10eab819563e6c3b3e420e58c35faffa8 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 12:59:09 -0400 Subject: [PATCH 101/119] chore: Rename test_file.py as well --- pkg-py/tests/{test_path.py => test_file.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg-py/tests/{test_path.py => test_file.py} (100%) diff --git a/pkg-py/tests/test_path.py b/pkg-py/tests/test_file.py similarity index 100% rename from pkg-py/tests/test_path.py rename to pkg-py/tests/test_file.py From ab6ddde89de2c5b05d54ad03f7023880bbe4e156 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 13:24:02 -0400 Subject: [PATCH 102/119] feat(FileLocationLocal): Now has absolute, relative, exists methods Restores behavior where local files are primarily represented as relative to the source `_brand.yml` file, but carry the knowledge of the root directory such that both representations are possible --- pkg-py/src/brand_yaml/__init__.py | 53 ++++++++++-------------- pkg-py/src/brand_yaml/file.py | 68 +++++++++++++++++++++++++------ pkg-py/tests/test_brand.py | 62 +++++++++++++++++++++------- pkg-py/tests/test_file.py | 31 ++++++++++++-- 4 files changed, 152 insertions(+), 62 deletions(-) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 250f8aa2..1801418c 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -3,7 +3,13 @@ from pathlib import Path from typing import Any, Literal, overload -from pydantic import BaseModel, ConfigDict, Field, model_validator +from pydantic import ( + BaseModel, + ConfigDict, + Field, + field_validator, + model_validator, +) from ruamel.yaml import YAML from ._utils import find_project_brand_yaml, recurse_dicts_and_models @@ -116,46 +122,31 @@ def resolve_typography_colors(self): return self - def paths_make_absolute(self): - """ - Make all paths in the brand absolute. - - Finds all fields that expect file paths in `logo` and `typography` and - converts these local file paths to local absolute file paths. In a - `_brand.yml` file, these paths are specified relative to the directory - containing the source YAML file. This method converts all local file - paths to be absolute, provided the Brand was read initially from a YAML - file via :meth:`brand_yaml.Brand.from_yaml` or - :func:`brand_yaml.read_brand_yaml`. - """ + @field_validator("path", mode="after") + @classmethod + def validate_path_is_absolute(cls, value: Path | None) -> Path | None: + if value is None: + return None - path = self.path - if path is not None: - recurse_dicts_and_models( - self, - pred=lambda value: isinstance(value, FileLocationLocal), - modify=lambda value: value.make_absolute(path.parent), + value = Path(value).expanduser() + + if not value.is_absolute(): + raise ValueError( + f"brand.path must be an absolute path, not `{value}`." ) - return self - def paths_make_relative(self): - """ - Make all paths in the brand relative. - - Finds all fields that expect file paths in `logo` and `typography` and - converts absolute file paths to local file paths, relative to the source - YAML file, provided the Brand was read initially from a YAML file via - :meth:`brand_yaml.Brand.from_yaml` or - :func:`brand_yaml.read_brand_yaml`. - """ + return value.resolve() + @model_validator(mode="after") + def set_root_path(self): path = self.path if path is not None: recurse_dicts_and_models( self, pred=lambda value: isinstance(value, FileLocationLocal), - modify=lambda value: value.make_relative(path.parent), + modify=lambda value: value.set_root_dir(path.parent), ) + return self diff --git a/pkg-py/src/brand_yaml/file.py b/pkg-py/src/brand_yaml/file.py index 0ef35b03..ca1db3a0 100644 --- a/pkg-py/src/brand_yaml/file.py +++ b/pkg-py/src/brand_yaml/file.py @@ -1,7 +1,8 @@ from __future__ import annotations +from copy import copy from pathlib import Path -from typing import Union +from typing import Any, Union from pydantic import HttpUrl, RootModel, field_validator @@ -31,24 +32,65 @@ class FileLocationUrl(FileLocation): class FileLocationLocal(FileLocation): root: Path + _root_dir: Path | None = None - def make_absolute(self, relative_to: str | Path = Path(".")): + def __init__(self, root: Path): + super().__init__(root) + + @field_validator("root", mode="after") + @classmethod + def validate_not_absolute(cls, value: Path) -> Path: + v = value.expanduser() + if v.is_absolute(): + raise ValueError( + "Local paths must be relative to the Brand YAML source file. " + + f"Use 'file://{v}' if you are certain you want to use " + + "an absolute path for a local file." + ) + + return value + + def __copy__(self): + m = super().__copy__() + m._root_dir = copy(self._root_dir) + return m + + def __deepcopy__(self, memo: dict[int, Any] | None = None): + m = super().__deepcopy__(memo) + m._root_dir = copy(self._root_dir) + return m + + def set_root_dir(self, root_dir: Path) -> None: + self._root_dir = root_dir + + def absolute(self) -> Path: if self.root.is_absolute(): - return + return self.root - relative_to = Path(relative_to).absolute() - self.root = relative_to / self.root + if self._root_dir is None: + return self.root.absolute() - def make_relative(self, relative_to: str | Path = Path(".")): - if not self.root.is_absolute(): - return + relative_to = Path(self._root_dir).absolute() + return relative_to / self.root - relative_to = Path(relative_to).absolute() - self.root = self.root.relative_to(relative_to) + def relative(self) -> Path: + if not self.root.is_absolute() or self._root_dir is None: + return self.root - def validate_path_exists(self) -> None: - if not self.root.exists(): - raise FileNotFoundError(f"File not found: {self.root}") + relative_to = Path(self._root_dir).absolute() + return self.root.relative_to(relative_to) + + def exists(self) -> bool: + return self.absolute().exists() + + def validate_exists( + self, + relative_to: str | Path | None = None, + ) -> None: + if not self.exists(): + raise FileNotFoundError( + f"File '{self.root}' not found at '{self.absolute()}'" + ) FileLocationLocalOrUrl = Union[FileLocationUrl, FileLocationLocal] diff --git a/pkg-py/tests/test_brand.py b/pkg-py/tests/test_brand.py index fa27060b..57c9921c 100644 --- a/pkg-py/tests/test_brand.py +++ b/pkg-py/tests/test_brand.py @@ -42,29 +42,61 @@ def test_brand_yml_paths(): brand = read_brand_yaml(path) assert isinstance(brand.logo, BrandLogo) + assert isinstance(brand.logo.small, FileLocationLocal) assert isinstance(brand.typography, BrandTypography) assert isinstance(brand.typography.fonts, list) assert isinstance(brand.typography.fonts[0], BrandTypographyFontFiles) + assert isinstance( + brand.typography.fonts[0].files[0].path, FileLocationLocal + ) - # Paths are all relative initially - assert str(brand.logo.small) == "does-not-exist.png" - assert str(brand.typography.fonts[0].files[0].path) == "Invisible.ttf" + # Paths are all relative + assert brand.logo.small.root == Path("does-not-exist.png") + assert brand.logo.small.root == brand.logo.small.relative() - # but can be made absolute with a method call - brand.paths_make_absolute() - assert isinstance(brand.logo.small, FileLocationLocal) - assert brand.logo.small.root == path.absolute() / "does-not-exist.png" - assert isinstance( - brand.typography.fonts[0].files[0].path, - FileLocationLocal, + assert brand.typography.fonts[0].files[0].path.root == Path("Invisible.ttf") + assert ( + brand.typography.fonts[0].files[0].path.root + == brand.typography.fonts[0].files[0].path.relative() ) + + # Paths can be accessed absolutely + assert ( + brand.logo.small.absolute() == (path / "does-not-exist.png").absolute() + ) + assert ( + brand.typography.fonts[0].files[0].path.absolute() + == (path / "Invisible.ttf").absolute() + ) + + # These files don't exist, which can be verified + assert not brand.logo.small.exists() + assert not brand.typography.fonts[0].files[0].path.exists() + + # Updating brand.path updates the paths in the brand ----------------------- + brand.path = path_fixtures / "_brand.yml" + + # Paths are still all relative + assert brand.logo.small.root == Path("does-not-exist.png") + assert brand.logo.small.root == brand.logo.small.relative() + + assert brand.typography.fonts[0].files[0].path.root == Path("Invisible.ttf") assert ( brand.typography.fonts[0].files[0].path.root - == path.absolute() / "Invisible.ttf" + == brand.typography.fonts[0].files[0].path.relative() + ) + + # Absolute paths have now been updated + assert ( + brand.logo.small.absolute() + == (path_fixtures / "does-not-exist.png").absolute() + ) + assert ( + brand.typography.fonts[0].files[0].path.absolute() + == (path_fixtures / "Invisible.ttf").absolute() ) - # which can be reversed again back to relative paths (possibly destructive) - brand.paths_make_relative() - assert str(brand.logo.small) == "does-not-exist.png" - assert str(brand.typography.fonts[0].files[0].path) == "Invisible.ttf" + # brand.path must be absolute + with pytest.raises(ValueError): + brand.path = Path("_brand.yml") diff --git a/pkg-py/tests/test_file.py b/pkg-py/tests/test_file.py index 13a46d73..90c20afb 100644 --- a/pkg-py/tests/test_file.py +++ b/pkg-py/tests/test_file.py @@ -1,5 +1,6 @@ from __future__ import annotations +import copy from pathlib import Path import pytest @@ -29,9 +30,33 @@ def test_local_file_path_resolution(): assert isinstance(local, FileLocation) assert isinstance(local, FileLocationLocal) assert str(local) == "fancy-logo.png" + assert local.relative() == Path("fancy-logo.png") - local.make_absolute(Path(__file__).parent) - assert str(local) == str(Path(__file__).parent / "fancy-logo.png") + local.set_root_dir(Path(__file__).parent) + assert local.absolute() == Path(__file__).parent / "fancy-logo.png" + + assert not local.exists() with pytest.raises(FileNotFoundError): - local.validate_path_exists() + local.validate_exists() + + +def test_local_file_cannot_be_absolute(): + with pytest.raises(ValueError, match="file:///"): + FileLocationLocal.model_validate("/fancy-logo.png") + + with pytest.raises(ValueError, match="file:///"): + FileLocationLocal.model_validate("~/fancy-logo.png") + + +def test_local_files_retain_root_after_copy(): + local = FileLocationLocal.model_validate("fancy-logo.png") + local.set_root_dir(Path(__file__).parent) + + local_copy = copy.copy(local) + assert local_copy.absolute() == local.absolute() + assert local_copy.model_dump() == local.model_dump() + + local_deep = copy.deepcopy(local) + assert local_deep.absolute() == local.absolute() + assert local_deep.model_dump() == local.model_dump() From f553a746488e3be70afaaf3f2e5b776b8276ba95 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 13:57:38 -0400 Subject: [PATCH 103/119] chore: Remove `BrandWith` --- pkg-py/src/brand_yaml/_defs.py | 52 +------------- pkg-py/tests/test_defs.py | 125 ++------------------------------- 2 files changed, 7 insertions(+), 170 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index f80d38b8..a050786c 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -7,9 +7,6 @@ from pydantic import ( BaseModel, ConfigDict, - Field, - field_validator, - model_validator, ) from ._utils_logging import logger @@ -28,55 +25,11 @@ class BrandLightDark(BaseModel, Generic[T]): dark: T | None = None -class BrandLightDarkString(BrandLightDark[str]): - pass - - -LeafNode = Union[str, float, int, bool, None] - - def is_leaf_node(value: Any) -> bool: # Note: We treat iterables as leaf nodes return not isinstance(value, (dict, BaseModel)) -def is_non_str_leaf_node(value: Any) -> bool: - return not isinstance(value, (dict, BaseModel, str)) - - -class BrandWith(BaseModel, Generic[T]): - model_config = ConfigDict( - extra="ignore", - str_strip_whitespace=True, - populate_by_name=True, - revalidate_instances="always", - validate_assignment=True, - ) - - with_: dict[str, T] | None = Field(default=None, alias="with") - - @field_validator("with_", mode="after") - @classmethod - def validate_with_(cls, value: dict[str, T] | None) -> dict[str, T] | None: - if value is None: - return value - - logger.debug( - "validating field with_ by checking for circular references" - ) - check_circular_references(value, name="with") - return value - - @model_validator(mode="after") - def resolve_with_values(self): - if self.with_ is None: - return self - - logger.debug("validating model and resolving with_ values") - defs_replace_recursively(self, defs=self.with_, name="with_") - return self - - def defs_get( defs: DictStringRecursiveBaseModel, key: str, level: int = 0 ) -> object: @@ -256,9 +209,8 @@ def check_circular_references( path_key = [*path, key] - if isinstance( - value, str - ): # implied value is also in data by above check + # implied value is also in data by above check + if isinstance(value, str): seen_key = [*seen, *([key, value] if len(seen) == 0 else [value])] if value in seen: raise CircularReferenceError(seen_key, path_key, name) diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index fe75bd27..efba59f6 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -1,132 +1,17 @@ from __future__ import annotations -from typing import Union - import pytest -from brand_yaml._defs import ( - BrandLightDarkString, - BrandWith, - CircularReferenceError, -) - -# from brand_yaml._utils_logging import log_set_debug -# log_set_debug() - - -def test_brand_with_simple(): - class BrandThing(BrandWith[str]): - small: str | None = None - medium: str | None = None - large: str | None = None - - thing = BrandThing.model_validate( - { - "with_": {"sm": "small", "md": "medium", "lg": "large"}, - "small": "sm", - "medium": "md", - "large": "lg", - } - ) - - assert thing.small == "small" - assert thing.medium == "medium" - assert thing.large == "large" - - -def test_brand_with_simple_mixed(): - class BrandThing(BrandWith[str]): - small: str | None = None - medium: str | None = None - large: str | None = None - - thing = BrandThing.model_validate( - { - "with_": {"sm": "small", "md": "medium", "lg": "large"}, - "small": "lg", - "medium": "middle", - "large": "LG", - } - ) - - assert thing.small == "large" - assert thing.medium == "middle" - assert thing.large == "LG" - - -def test_brand_with_dict(): - class BrandThing(BrandWith[str]): - small: dict[str, str] | None = None - medium: dict[str, str] | None = None - - thing = BrandThing.model_validate( - { - "with_": {"sm": "small", "md": "medium", "lg": "large"}, - "small": {"one": "sm", "two": "md"}, - "medium": {"three": "md"}, - } - ) - - assert thing.small == {"one": "small", "two": "medium"} - assert thing.medium == {"three": "medium"} - - -def test_brand_with_basemodel(): - class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): - small: str | BrandLightDarkString | None = None - medium: str | BrandLightDarkString | None = None - - thing = BrandThing.model_validate( - { - "with_": { - "sm": "small-light", - "md": {"light": "medium-light", "dark": "medium-dark"}, - }, - "small": {"light": "sm", "dark": "small-dark"}, - "medium": "md", - } - ) - - assert isinstance(thing.small, BrandLightDarkString) - assert isinstance(thing.medium, BrandLightDarkString) - - assert thing.small.light == "small-light" - assert thing.small.dark == "small-dark" - assert thing.medium.light == "medium-light" - assert thing.medium.dark == "medium-dark" - - -def test_brand_with_nested(): - class BrandThing(BrandWith[Union[str, BrandLightDarkString]]): - the: str | BrandLightDarkString | None = None - - thing = BrandThing.model_validate( - { - "with_": { - "light": "the-light", - "dark": "the-dark", - "both": {"light": "the-light", "dark": "the-dark"}, - }, - "the": "both", - } - ) - - assert thing.with_ is not None - assert isinstance(thing.with_["both"], BrandLightDarkString) - assert isinstance(thing.the, BrandLightDarkString) - - assert thing.the == thing.with_["both"] - assert thing.with_["both"].light == "the-light" - assert thing.with_["both"].dark == "the-dark" +from brand_yaml._defs import CircularReferenceError, check_circular_references def test_brand_with_errors_on_circular_references(): with pytest.raises(CircularReferenceError, match="a -> b -> a"): - BrandWith.model_validate({"with_": {"a": "b", "b": "a"}}) + check_circular_references({"a": "b", "b": "a"}) with pytest.raises(CircularReferenceError, match="a -> b -> c -> a"): - BrandWith.model_validate({"with_": {"a": "b", "b": "c", "c": "a"}}) + check_circular_references({"a": "b", "b": "c", "c": "a"}) with pytest.raises(CircularReferenceError, match="a -> d -> b -> a"): - BrandWith.model_validate( - {"with_": {"a": "d", "b": "a", "d": {"x": "e", "y": "b"}}} + check_circular_references( + {"a": "d", "b": "a", "d": {"x": "e", "y": "b"}} ) From ada04de3fb8cceb78bdcd49d6f371f2274f9658f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 14:07:53 -0400 Subject: [PATCH 104/119] chore(check_circular_references): Stronger typing --- pkg-py/src/brand_yaml/_defs.py | 2 +- pkg-py/tests/test_defs.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index a050786c..d9cfc9bd 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -176,7 +176,7 @@ def get_value(items: dict | BaseModel, key: str) -> object: def check_circular_references( - data: Any, + data: dict[str, Any], current: object | None = None, seen: list[str] | None = None, path: list[str] | None = None, diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index efba59f6..e7d31769 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -13,5 +13,5 @@ def test_brand_with_errors_on_circular_references(): with pytest.raises(CircularReferenceError, match="a -> d -> b -> a"): check_circular_references( - {"a": "d", "b": "a", "d": {"x": "e", "y": "b"}} + {"a": "d", "b": "a", "d": {"x": "e", "y": {"y1": "b"}}} ) From 74841975cd666a207518bfb36e66b210ce56428f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 14:20:39 -0400 Subject: [PATCH 105/119] chore(defs_replace_recursively): Improve typing --- pkg-py/src/brand_yaml/_defs.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index d9cfc9bd..399a4e24 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -73,7 +73,7 @@ def defs_get( def defs_replace_recursively( items: dict | BaseModel | None, - defs: Any = None, + defs: dict | None = None, level: int = 0, name: str | None = None, exclude: str | None = None, @@ -103,7 +103,12 @@ def defs_replace_recursively( definition. """ if defs is None: - defs = items + if isinstance(items, dict): + defs = items + else: + raise ValueError( + "When `defs` is `None`, `items` must be a dictionary." + ) if level == 0: logger.debug("Checking for circular references") @@ -137,7 +142,6 @@ def defs_replace_recursively( elif isinstance(items, dict): items[key] = new_value elif isinstance(value, (dict, BaseModel)): - # TODO: we may want to avoid recursing into child BrandWith instances logger.debug(level_indent(f"recursing into {key}", level)) defs_replace_recursively( value, From fbca3766aba04d2a4ce063fcfc7c6e3e7713751f Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 16:55:30 -0400 Subject: [PATCH 106/119] chore: add and improve coverage --- .gitignore | 3 +- Makefile | 13 ++++ pkg-py/src/brand_yaml/_defs.py | 12 ++-- pkg-py/src/brand_yaml/logo.py | 5 +- pkg-py/src/brand_yaml/typography.py | 4 +- .../fixtures/find-brand-dir/brand/_brand.yml | 2 + pkg-py/tests/fixtures/find-brand-dir/empty.py | 0 pkg-py/tests/test_brand.py | 13 +++- pkg-py/tests/test_defs.py | 52 +++++++++++++- pkg-py/tests/test_logo.py | 15 ++++ pyproject.toml | 1 + uv.lock | 71 +++++++++++++++++++ 12 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 pkg-py/tests/fixtures/find-brand-dir/brand/_brand.yml create mode 100644 pkg-py/tests/fixtures/find-brand-dir/empty.py diff --git a/.gitignore b/.gitignore index ed8ebf58..d8a1bb09 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -__pycache__ \ No newline at end of file +__pycache__ +.coverage diff --git a/Makefile b/Makefile index c5901905..f99b880a 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,19 @@ py-format: ## [py] Format python code uv run ruff check --fix pkg-py --config pyproject.toml uv run ruff format pkg-py --config pyproject.toml +.PHONY: py-coverage +py-coverage: ## [py] Generate coverage report + @echo "📔 Generating coverage report" + uv run coverage run -m pytest pkg-py/tests + uv run coverage report + +.PHONY: py-coverage-report +py-coverage-report: py-coverage ## [py] Generate coverage report and open it in browser + uv run coverage html + @echo "" + @echo "📡 Serving coverage report at http://localhost:8081/" + @npx http-server htmlcov --silent -p 8081 + .PHONY: py-update-snaps py-update-snaps: ## [py] Update python test snapshots @echo "📸 Updating pytest snapshots" diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 399a4e24..1bb1c79d 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -102,10 +102,13 @@ def defs_replace_recursively( refer to definitions in `defs`, the are replaced with copies of the definition. """ + if items is None: + return None + if defs is None: if isinstance(items, dict): defs = items - else: + else: # pragma: no cover raise ValueError( "When `defs` is `None`, `items` must be a dictionary." ) @@ -114,13 +117,10 @@ def defs_replace_recursively( logger.debug("Checking for circular references") check_circular_references(defs, name=name) - if level > 50: + if level > 50: # pragma: no cover logger.error("BrandWith recursion limit reached") return - if not isinstance(items, (dict, BaseModel)): - return - for key in item_keys(items): value = get_value(items, key) @@ -190,7 +190,7 @@ def check_circular_references( seen = seen if seen is not None else [] path = path if path is not None else [] - if not isinstance(current, (dict, BaseModel)): + if not isinstance(current, (dict, BaseModel)): # pragma: no cover if not isinstance(current, str): raise ValueError( "All values must be strings, dictionaries, or pydantic models." diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 646698bb..502a37fa 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -1,5 +1,6 @@ from __future__ import annotations +from pathlib import Path from typing import Annotated, Any, Union from pydantic import ( @@ -11,7 +12,7 @@ from ._defs import BrandLightDark, defs_replace_recursively from .base import BrandBase -from .file import FileLocationLocalOrUrl +from .file import FileLocation, FileLocationLocalOrUrl BrandLogoFileType = Annotated[ Union[ @@ -75,7 +76,7 @@ def resolve_image_values(cls, data: Any): raise ValueError("images must be a dictionary of file locations") for key, value in images.items(): - if not isinstance(value, (str, FileLocationLocalOrUrl)): + if not isinstance(value, (str, FileLocation, Path)): raise ValueError(f"images[{key}] must be a file location") defs_replace_recursively(data, defs=images, name="logo") diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 65d757dd..08ab0ff8 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -222,7 +222,7 @@ def to_str_url(self) -> str: return f"{self.root[0]}..{self.root[1]}" return str(self.root) - if TYPE_CHECKING: + if TYPE_CHECKING: # pragma: no cover # https://docs.pydantic.dev/latest/concepts/serialization/#overriding-the-return-type-when-dumping-a-model # Ensure type checkers see the correct return type def model_dump( @@ -388,7 +388,7 @@ def validate_weight(cls, value: Any) -> list[BrandTypographyFontWeightInt]: value = [font_weight_map[v] if isinstance(v, str) else v for v in value] return value - if TYPE_CHECKING: + if TYPE_CHECKING: # pragma: no cover # https://docs.pydantic.dev/latest/concepts/serialization/#overriding-the-return-type-when-dumping-a-model # Ensure type checkers see the correct return type def model_dump( diff --git a/pkg-py/tests/fixtures/find-brand-dir/brand/_brand.yml b/pkg-py/tests/fixtures/find-brand-dir/brand/_brand.yml new file mode 100644 index 00000000..10e9168a --- /dev/null +++ b/pkg-py/tests/fixtures/find-brand-dir/brand/_brand.yml @@ -0,0 +1,2 @@ +meta: + name: Test if found automatically diff --git a/pkg-py/tests/fixtures/find-brand-dir/empty.py b/pkg-py/tests/fixtures/find-brand-dir/empty.py new file mode 100644 index 00000000..e69de29b diff --git a/pkg-py/tests/test_brand.py b/pkg-py/tests/test_brand.py index 57c9921c..9a4883ce 100644 --- a/pkg-py/tests/test_brand.py +++ b/pkg-py/tests/test_brand.py @@ -2,7 +2,7 @@ from pathlib import Path import pytest -from brand_yaml import read_brand_yaml +from brand_yaml import Brand, read_brand_yaml from brand_yaml.file import FileLocationLocal from brand_yaml.logo import BrandLogo from brand_yaml.typography import BrandTypography, BrandTypographyFontFiles @@ -13,12 +13,21 @@ def test_brand_yml_found_in_dir(): path = path_fixtures / "find-brand-yml" / "_brand.yml" - brand_direct = read_brand_yaml(path) + brand_direct = Brand.from_yaml(path) brand_found = read_brand_yaml(path.parent) assert brand_found == brand_direct +def test_brand_yml_found_in_subdir(): + path = path_fixtures / "find-brand-dir" / "empty.py" + + brand_direct = Brand.from_yaml(path.parent / "brand" / "_brand.yml") + brand_found = read_brand_yaml(path) + + assert brand_found == brand_direct + + def test_brand_yml_found_from_py_file(): path = Path(__file__).parent / "fixtures" / "find-brand-yml" / "_brand.yml" diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index e7d31769..2b8bbf77 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -1,7 +1,13 @@ from __future__ import annotations import pytest -from brand_yaml._defs import CircularReferenceError, check_circular_references +from brand_yaml._defs import ( + CircularReferenceError, + check_circular_references, + defs_get, + defs_replace_recursively, +) +from pydantic import BaseModel def test_brand_with_errors_on_circular_references(): @@ -15,3 +21,47 @@ def test_brand_with_errors_on_circular_references(): check_circular_references( {"a": "d", "b": "a", "d": {"x": "e", "y": {"y1": "b"}}} ) + + try: + check_circular_references({"a": "b", "b": 2}) + except Exception: + assert False, "should not raise an error " + + +class MyThing(BaseModel): + a: str | int + b: str | int + c: dict[str, str] + d: dict[str, str | MySubThing] + + +class MySubThing(BaseModel): + s1: str | None = None + s2: str | None = None + + +def test_defs_replace_recursively(): + items = MyThing( + a="a", + b="b", + c={"c1": "x", "c2": "y"}, + d={"d1": MySubThing(s1="s1", s2="s2")}, + ) + defs = {"a": 1, "b": 2, "x": "X", "s2": MySubThing(s2="SS22")} + + assert defs_get(defs, "y") == "y" + + defs_replace_recursively(items, defs) + assert items.a == 1 + assert items.b == 2 + assert items.c == {"c1": "X", "c2": "y"} + assert isinstance(items.d["d1"], MySubThing) + assert items.d["d1"].model_dump(warnings=False) == { + "s1": "s1", + "s2": {"s1": None, "s2": "SS22"}, + } + + with pytest.raises(ValueError, match="must be a dictionary"): + defs_replace_recursively(MySubThing(s2="foo")) + + assert defs_replace_recursively(None) is None diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 1a650274..595b8bbe 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -22,6 +22,21 @@ def test_brand_logo_single(): assert brand.logo == "posit.png" +def test_brand_logo_errors(): + with pytest.raises(ValueError): + BrandLogo.model_validate("foo") + + with pytest.raises(ValueError): + BrandLogo.model_validate({"images": "foo"}) + + with pytest.raises(ValueError): + BrandLogo.model_validate({"images": {"light": 1234}}) + + +def test_brand_logo_images_accept_paths(): + bl = BrandLogo.model_validate({"images": {"cat": Path("cat.jpg")}}) + + def test_brand_logo_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-logo-simple.yml")) diff --git a/pyproject.toml b/pyproject.toml index 36b0ccdc..94c8a176 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dev-dependencies = [ "nbformat>=5.10.4", "nbclient>=0.10.0", "ipykernel>=6.29.5", + "coverage>=7.6.1", ] [build-system] diff --git a/uv.lock b/uv.lock index 0d7b6382..034b4f6d 100644 --- a/uv.lock +++ b/uv.lock @@ -64,6 +64,7 @@ test = [ [package.dev-dependencies] dev = [ + { name = "coverage" }, { name = "ipykernel" }, { name = "nbclient" }, { name = "nbformat" }, @@ -85,6 +86,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "coverage", specifier = ">=7.6.1" }, { name = "ipykernel", specifier = ">=6.29.5" }, { name = "nbclient", specifier = ">=0.10.0" }, { name = "nbformat", specifier = ">=5.10.4" }, @@ -201,6 +203,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/75/49e5bfe642f71f272236b5b2d2691cf915a7283cc0ceda56357b61daa538/comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3", size = 7180 }, ] +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690 }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127 }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654 }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598 }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732 }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816 }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325 }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418 }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343 }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136 }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796 }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244 }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279 }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859 }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549 }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477 }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134 }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910 }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348 }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230 }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983 }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221 }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342 }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371 }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455 }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924 }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252 }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897 }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606 }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373 }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007 }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269 }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886 }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037 }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038 }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690 }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765 }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611 }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671 }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368 }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758 }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035 }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839 }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569 }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927 }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401 }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301 }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598 }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307 }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453 }, + { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688 }, + { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120 }, + { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249 }, + { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237 }, + { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311 }, + { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453 }, + { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958 }, + { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938 }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352 }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153 }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926 }, +] + [[package]] name = "debugpy" version = "1.8.5" From 18a77c96b3f758fec46cbb70e1aa7c2211c83b25 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 17:02:51 -0400 Subject: [PATCH 107/119] chore: docs remove refs to `with_` --- pkg-py/src/brand_yaml/_defs.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 1bb1c79d..5ca7d177 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -31,11 +31,13 @@ def is_leaf_node(value: Any) -> bool: def defs_get( - defs: DictStringRecursiveBaseModel, key: str, level: int = 0 + defs: DictStringRecursiveBaseModel, + key: str, + level: int = 0, ) -> object: """ - Finds `key` in `with_`, which may require recursively resolving nested - values from `with_`. + Finds `key` in `deps`, which may require recursively resolving nested + values in `deps`. Parameters ---------- @@ -49,9 +51,10 @@ def defs_get( Returns ------- : - The value of `key` in `defs`, with any internal references to top-level - keys in `defs` also resolved. If `defs[key]` returns a dictionary or - pydantic model, internal references to definitions are also replaced. + The a deep copy of the value of `key` in `defs`, with any internal + references to top-level keys in `defs` also resolved. If `defs[key]` + returns a dictionary or pydantic model, internal references to + definitions are also replaced. """ if key not in defs: return key @@ -61,7 +64,7 @@ def defs_get( with_value = deepcopy(defs[key]) logger.debug( - level_indent(f"key {key} is in with_ with value {with_value!r}", level) + level_indent(f"key {key} is in defs with value {with_value!r}", level) ) if isinstance(with_value, (dict, BaseModel)): @@ -118,7 +121,7 @@ def defs_replace_recursively( check_circular_references(defs, name=name) if level > 50: # pragma: no cover - logger.error("BrandWith recursion limit reached") + logger.error("Hit recursion limit recursing into `items`") return for key in item_keys(items): From 7a1d9677481efda6dad46bbdb562d8e5f1050c76 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Thu, 3 Oct 2024 17:36:12 -0400 Subject: [PATCH 108/119] chore: fix format issue --- pkg-py/tests/test_logo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 595b8bbe..92cccc75 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -34,7 +34,7 @@ def test_brand_logo_errors(): def test_brand_logo_images_accept_paths(): - bl = BrandLogo.model_validate({"images": {"cat": Path("cat.jpg")}}) + BrandLogo.model_validate({"images": {"cat": Path("cat.jpg")}}) def test_brand_logo_ex_simple(snapshot_json): From 0ae4fb00eeb931219e53b3911ba6f3d41aee35f7 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 09:38:01 -0400 Subject: [PATCH 109/119] chore(defs_replace_recursively): Make `defs` a required argument --- pkg-py/src/brand_yaml/_defs.py | 10 +--------- pkg-py/src/brand_yaml/color.py | 4 ++-- pkg-py/tests/test_defs.py | 5 +---- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 5ca7d177..5ca84f66 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -76,7 +76,7 @@ def defs_get( def defs_replace_recursively( items: dict | BaseModel | None, - defs: dict | None = None, + defs: dict, level: int = 0, name: str | None = None, exclude: str | None = None, @@ -108,14 +108,6 @@ def defs_replace_recursively( if items is None: return None - if defs is None: - if isinstance(items, dict): - defs = items - else: # pragma: no cover - raise ValueError( - "When `defs` is `None`, `items` must be a dictionary." - ) - if level == 0: logger.debug("Checking for circular references") check_circular_references(defs, name=name) diff --git a/pkg-py/src/brand_yaml/color.py b/pkg-py/src/brand_yaml/color.py index b67b748d..66a73021 100644 --- a/pkg-py/src/brand_yaml/color.py +++ b/pkg-py/src/brand_yaml/color.py @@ -99,7 +99,7 @@ def create_brand_palette(cls, value: dict[str, str] | None): # We resolve `color.palette` on load or on replacement only # TODO: Replace with class with getter/setters # Retain original values, return resolved values, and re-validate on update. - defs_replace_recursively(value, name="palette") + defs_replace_recursively(value, value, name="palette") return value @@ -114,7 +114,7 @@ def _color_defs(self, resolved: bool = False) -> dict[str, str]: ) if resolved: - defs_replace_recursively(defs) + defs_replace_recursively(defs, defs) return defs else: return defs diff --git a/pkg-py/tests/test_defs.py b/pkg-py/tests/test_defs.py index 2b8bbf77..7d6e0f81 100644 --- a/pkg-py/tests/test_defs.py +++ b/pkg-py/tests/test_defs.py @@ -61,7 +61,4 @@ def test_defs_replace_recursively(): "s2": {"s1": None, "s2": "SS22"}, } - with pytest.raises(ValueError, match="must be a dictionary"): - defs_replace_recursively(MySubThing(s2="foo")) - - assert defs_replace_recursively(None) is None + assert defs_replace_recursively(None, {}) is None From 87e8be21b72e5728238af7b605c2c8f8e6d43bbd Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 09:49:57 -0400 Subject: [PATCH 110/119] chore(defs): Replace `is_leaf_node()` with typeguard for `dict | BaseModel` --- pkg-py/src/brand_yaml/_defs.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 5ca84f66..6353cf89 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -8,6 +8,7 @@ BaseModel, ConfigDict, ) +from typing_extensions import TypeGuard from ._utils_logging import logger @@ -25,9 +26,8 @@ class BrandLightDark(BaseModel, Generic[T]): dark: T | None = None -def is_leaf_node(value: Any) -> bool: - # Note: We treat iterables as leaf nodes - return not isinstance(value, (dict, BaseModel)) +def is_dict_or_basemodel(value: Any) -> TypeGuard[Union[dict, BaseModel]]: + return isinstance(value, (dict, BaseModel)) def defs_get( @@ -67,7 +67,7 @@ def defs_get( level_indent(f"key {key} is in defs with value {with_value!r}", level) ) - if isinstance(with_value, (dict, BaseModel)): + if is_dict_or_basemodel(with_value): defs_replace_recursively(with_value, defs=defs, level=level) return with_value else: @@ -136,7 +136,7 @@ def defs_replace_recursively( setattr(items, key, new_value) elif isinstance(items, dict): items[key] = new_value - elif isinstance(value, (dict, BaseModel)): + elif is_dict_or_basemodel(value): logger.debug(level_indent(f"recursing into {key}", level)) defs_replace_recursively( value, @@ -185,7 +185,7 @@ def check_circular_references( seen = seen if seen is not None else [] path = path if path is not None else [] - if not isinstance(current, (dict, BaseModel)): # pragma: no cover + if not is_dict_or_basemodel(current): # pragma: no cover if not isinstance(current, str): raise ValueError( "All values must be strings, dictionaries, or pydantic models." @@ -203,7 +203,7 @@ def check_circular_references( if isinstance(value, str): if value not in data: continue - elif is_leaf_node(value): + elif not is_dict_or_basemodel(value): continue path_key = [*path, key] From 5be99f74eef71b2e66395e18706223bfbb4a6cc5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 09:59:12 -0400 Subject: [PATCH 111/119] chore: coverage settings --- Makefile | 2 +- pyproject.toml | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f99b880a..ac6fd9be 100644 --- a/Makefile +++ b/Makefile @@ -45,7 +45,7 @@ py-format: ## [py] Format python code .PHONY: py-coverage py-coverage: ## [py] Generate coverage report @echo "📔 Generating coverage report" - uv run coverage run -m pytest pkg-py/tests + uv run coverage run -m pytest uv run coverage report .PHONY: py-coverage-report diff --git a/pyproject.toml b/pyproject.toml index 94c8a176..905b90e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,6 +45,24 @@ packages = ["pkg-py/src/brand_yaml"] include = ["pkg-py"] exclude = ["pkg-py/_dev", "pkg-py/.venv"] +[tool.coverage.paths] +source = [ + "pkg-py/src/brand_yaml", +] + +[tool.coverage.run] +branch = true +source = [ + "pkg-py/src/brand_yaml", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "raise NotImplementedError", + "pass", +] + [tool.tox] legacy_tox_ini = """ [tox] From 6e8a59373ee6d1e1574a4e31ee74b60a3537c77e Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 10:54:24 -0400 Subject: [PATCH 112/119] tests(typography): Increase coverage --- pkg-py/src/brand_yaml/typography.py | 8 +- pkg-py/tests/test_typography.py | 214 +++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 214 insertions(+), 9 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 08ab0ff8..740b5f9c 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -333,7 +333,7 @@ def validate_path( cls, value: FileLocationLocalOrUrl ) -> FileLocationLocalOrUrl: ext = Path(str(value.root)).suffix - if not ext: + if not ext: # cover: for type checker raise BrandUnsupportedFontFileFormat(value.root) if ext not in font_formats: @@ -524,7 +524,7 @@ def _import_url_v2(self) -> str: values = [str(i) for i in ital] axis = "ital" - axis_range = f":{axis}@{';'.join(values)}" + axis_range = "" if len(values) == 0 else f":{axis}@{';'.join(values)}" params = urlencode( { "family": self.family + axis_range, @@ -678,7 +678,7 @@ class BrandTypography(BrandBase): @model_validator(mode="before") @classmethod def simple_google_fonts(cls, data: Any): - if not isinstance(data, dict): + if not isinstance(data, dict): # cover: for type checker return data defined_families = set() @@ -706,7 +706,7 @@ def simple_google_fonts(cls, data: Any): if field not in data: continue - if not isinstance(data[field], (str, dict)): + if not isinstance(data[field], (str, dict)): # pragma: no cover continue if isinstance(data[field], str): diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index d4ee9790..d0de9dbe 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -6,6 +6,7 @@ import pytest from brand_yaml import read_brand_yaml from brand_yaml.color import BrandColor +from brand_yaml.file import FileLocationLocal from brand_yaml.typography import ( BrandTypography, BrandTypographyBase, @@ -21,6 +22,7 @@ BrandTypographyMonospace, BrandTypographyMonospaceBlock, BrandTypographyMonospaceInline, + BrandUnsupportedFontFileFormat, validate_font_weight, ) from syrupy.extensions.json import JSONSnapshotExtension @@ -198,6 +200,73 @@ def test_brand_typography_fields_link(): } +def test_brand_typography_font_local_no_files(): + """ + A local font file source without any files can be used to signal that the + system font should be used. We expect no errors and for the font-inclusion + CSS to be empty. + """ + bf = BrandTypography.model_validate( + { + "fonts": [{"source": "file", "family": "Arial"}], + "base": "Arial", + } + ) + + assert isinstance(bf.fonts, list) + assert isinstance(bf.fonts[0], BrandTypographyFontFiles) + assert isinstance(bf.base, BrandTypographyBase) + assert bf.base.family == "Arial" + assert bf.css_include_fonts() == "" + + +@pytest.mark.parametrize("font_file", ["arial", "arial.pdf"]) +def test_brand_typography_font_file_ext_error(font_file): + with pytest.raises(ValueError): + BrandTypography.model_validate( + { + "fonts": [ + { + "source": "file", + "family": "Arial", + "files": [{"path": font_file}], + } + ] + } + ) + + with pytest.raises(ValueError): + BrandTypographyFontFilesPath.model_validate({"path": font_file}) + + with pytest.raises(BrandUnsupportedFontFileFormat): + bt = BrandTypographyFontFilesPath.model_validate({"path": "arial.ttf"}) + + assert isinstance(bt.path, FileLocationLocal) + bt.path.root = Path("arial") + bt.format + + +@pytest.mark.parametrize("font_file", ["arial.svg", "arial.eot", "arial.ttc"]) +def test_brand_typography_font_file_errors(font_file): + with pytest.raises(BrandUnsupportedFontFileFormat): + bt = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "file", + "family": "Arial", + "files": [{"path": font_file}], + } + ] + } + ) + + assert isinstance(bt.fonts[0], BrandTypographyFontFiles) + assert isinstance(bt.fonts[0].files, list) + assert isinstance(bt.fonts[0].files[0], BrandTypographyFontFilesPath) + bt.fonts[0].files[0].format + + def test_brand_typography_font_bunny(): bf = BrandTypography.model_validate( { @@ -238,6 +307,66 @@ def test_brand_typography_font_google_import_url(): == "https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,400;0,700;1,400;1,700&display=auto" ) + bg_no_weight = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "google", + "family": "Open Sans", + "weight": [], + "style": ["italic", "normal"], + } + ] + } + ) + + assert len(bg_no_weight.fonts) == 1 + assert isinstance(bg_no_weight.fonts[0], BrandTypographyFontGoogle) + assert ( + unquote(bg_no_weight.fonts[0].to_import_url()) + == "https://fonts.googleapis.com/css2?family=Open+Sans:ital@0;1&display=auto" + ) + + bg_no_ital = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "google", + "family": "Open Sans", + "weight": [700, 400], + "style": [], + } + ] + } + ) + + assert len(bg_no_ital.fonts) == 1 + assert isinstance(bg_no_ital.fonts[0], BrandTypographyFontGoogle) + assert ( + unquote(bg_no_ital.fonts[0].to_import_url()) + == "https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;700&display=auto" + ) + + bg_no_axes = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "google", + "family": "Open Sans", + "weight": [], + "style": [], + } + ] + } + ) + + assert len(bg_no_axes.fonts) == 1 + assert isinstance(bg_no_axes.fonts[0], BrandTypographyFontGoogle) + assert ( + unquote(bg_no_axes.fonts[0].to_import_url()) + == "https://fonts.googleapis.com/css2?family=Open+Sans&display=auto" + ) + def test_brand_typography_font_google_weight_range_import_url(): bg = BrandTypography.model_validate( @@ -253,7 +382,6 @@ def test_brand_typography_font_google_weight_range_import_url(): } ) - assert len(bg.fonts) == 1 assert isinstance(bg.fonts[0], BrandTypographyFontGoogle) assert isinstance(bg.fonts[0].weight, BrandTypographyGoogleFontsWeightRange) assert ( @@ -262,8 +390,24 @@ def test_brand_typography_font_google_weight_range_import_url(): ) +def test_brand_typography_font_google_weight_range_error(): + with pytest.raises(ValueError): + BrandTypography.model_validate( + { + "fonts": [ + { + "source": "google", + "family": "Open Sans", + "weight": "400..700..900", + "style": ["italic", "normal"], + } + ] + } + ) + + def test_brand_typography_font_bunny_import_url(): - bg = BrandTypography.model_validate( + bf = BrandTypography.model_validate( { "fonts": [ { @@ -276,13 +420,73 @@ def test_brand_typography_font_bunny_import_url(): } ) - assert len(bg.fonts) == 1 - assert isinstance(bg.fonts[0], BrandTypographyFontBunny) + assert len(bf.fonts) == 1 + assert isinstance(bf.fonts[0], BrandTypographyFontBunny) assert ( - unquote(bg.fonts[0].to_import_url()) + unquote(bf.fonts[0].to_import_url()) == "https://fonts.bunny.net/css?family=Open+Sans:400,400i,700,700i&display=auto" ) + bf_no_weight = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "bunny", + "family": "Open Sans", + "weight": [], + "style": ["italic", "normal"], + } + ] + } + ) + + assert len(bf_no_weight.fonts) == 1 + assert isinstance(bf_no_weight.fonts[0], BrandTypographyFontBunny) + assert ( + unquote(bf_no_weight.fonts[0].to_import_url()) + == "https://fonts.bunny.net/css?family=Open+Sans:regular,italic&display=auto" + ) + + bf_no_ital = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "bunny", + "family": "Open Sans", + "weight": [700, 400], + "style": [], + } + ] + } + ) + + assert len(bf_no_ital.fonts) == 1 + assert isinstance(bf_no_ital.fonts[0], BrandTypographyFontBunny) + assert ( + unquote(bf_no_ital.fonts[0].to_import_url()) + == "https://fonts.bunny.net/css?family=Open+Sans:400,700&display=auto" + ) + + bf_no_axes = BrandTypography.model_validate( + { + "fonts": [ + { + "source": "bunny", + "family": "Open Sans", + "weight": [], + "style": [], + } + ] + } + ) + + assert len(bf_no_axes.fonts) == 1 + assert isinstance(bf_no_axes.fonts[0], BrandTypographyFontBunny) + assert ( + unquote(bf_no_axes.fonts[0].to_import_url()) + == "https://fonts.bunny.net/css?family=Open+Sans&display=auto" + ) + def test_brand_typography_ex_simple(snapshot_json): brand = read_brand_yaml(path_examples("brand-typography-simple.yml")) diff --git a/pyproject.toml b/pyproject.toml index 905b90e9..f0b13170 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ source = [ [tool.coverage.report] exclude_lines = [ "pragma: no cover", + "cover: for type checker", "raise NotImplementedError", "pass", ] From 2690eaec615b376260a7c64d1331a96245e3fad5 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 11:00:10 -0400 Subject: [PATCH 113/119] chore(file): Remove unused init method --- pkg-py/src/brand_yaml/file.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg-py/src/brand_yaml/file.py b/pkg-py/src/brand_yaml/file.py index ca1db3a0..8301ad19 100644 --- a/pkg-py/src/brand_yaml/file.py +++ b/pkg-py/src/brand_yaml/file.py @@ -34,9 +34,6 @@ class FileLocationLocal(FileLocation): root: Path _root_dir: Path | None = None - def __init__(self, root: Path): - super().__init__(root) - @field_validator("root", mode="after") @classmethod def validate_not_absolute(cls, value: Path) -> Path: From c5dda8090b3cee1b9ffa7e5badcef8df02c000ae Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 11:28:23 -0400 Subject: [PATCH 114/119] chore: Move jsonschema to dev deps --- pyproject.toml | 10 +++++----- uv.lock | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f0b13170..4a9054a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ requires-python = ">=3.9" dependencies = [ "ruamel-yaml>=0.18.6", "pydantic>=2.8.2", - "jsonschema>=4.23.0", "eval-type-backport>=0.2.0", ] @@ -21,14 +20,15 @@ test = [ [tool.uv] dev-dependencies = [ # Actual dev deps + "coverage>=7.6.1", + "jsonschema>=4.23.0", "ruff>=0.6.5", "tox-uv>=1.11.4", # Dev work with Quarto + Positron - "pyyaml>=6.0.2", - "nbformat>=5.10.4", - "nbclient>=0.10.0", "ipykernel>=6.29.5", - "coverage>=7.6.1", + "nbclient>=0.10.0", + "nbformat>=5.10.4", + "pyyaml>=6.0.2", ] [build-system] diff --git a/uv.lock b/uv.lock index 034b4f6d..82216207 100644 --- a/uv.lock +++ b/uv.lock @@ -50,7 +50,6 @@ version = "0.0.1" source = { editable = "." } dependencies = [ { name = "eval-type-backport" }, - { name = "jsonschema" }, { name = "pydantic" }, { name = "ruamel-yaml" }, ] @@ -66,6 +65,7 @@ test = [ dev = [ { name = "coverage" }, { name = "ipykernel" }, + { name = "jsonschema" }, { name = "nbclient" }, { name = "nbformat" }, { name = "pyyaml" }, @@ -76,7 +76,6 @@ dev = [ [package.metadata] requires-dist = [ { name = "eval-type-backport", specifier = ">=0.2.0" }, - { name = "jsonschema", specifier = ">=4.23.0" }, { name = "pydantic", specifier = ">=2.8.2" }, { name = "pyright", marker = "extra == 'test'", specifier = ">=1.1.379" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8.3.2" }, @@ -88,6 +87,7 @@ requires-dist = [ dev = [ { name = "coverage", specifier = ">=7.6.1" }, { name = "ipykernel", specifier = ">=6.29.5" }, + { name = "jsonschema", specifier = ">=4.23.0" }, { name = "nbclient", specifier = ">=0.10.0" }, { name = "nbformat", specifier = ">=5.10.4" }, { name = "pyyaml", specifier = ">=6.0.2" }, From d3673d6f77c1470a651e1f47ea4f9da8ea24b5e4 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 11:29:34 -0400 Subject: [PATCH 115/119] chore(typography): Relocate custom error --- pkg-py/src/brand_yaml/typography.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 740b5f9c..0084c95b 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -142,6 +142,15 @@ def __init__(self, value: Any, allow_auto: bool = True): ) +class BrandUnsupportedFontFileFormat(ValueError): + supported = ("opentype", "truetype", "woff", "woff2") + + def __init__(self, value: Any): + super().__init__( + f"Unsupported font file {value!r}. Expected one of {', '.join(self.supported)}." + ) + + # Font Weights ----------------------------------------------------------------- @overload def validate_font_weight( @@ -192,15 +201,6 @@ def validate_font_weight( # Fonts (Files) ---------------------------------------------------------------- -class BrandUnsupportedFontFileFormat(ValueError): - supported = ("opentype", "truetype", "woff", "woff2") - - def __init__(self, value: Any): - super().__init__( - f"Unsupported font file {value!r}. Expected one of {', '.join(self.supported)}." - ) - - class BrandTypographyFontFileWeight(RootModel): root: ( BrandTypographyFontWeightSimpleAutoType From a18d5c5c78c2ee67275ce7af1c6d6a32da904dcc Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 14:23:28 -0400 Subject: [PATCH 116/119] chore: fix typos --- pkg-py/src/brand_yaml/_defs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/_defs.py b/pkg-py/src/brand_yaml/_defs.py index 6353cf89..ba11aa25 100644 --- a/pkg-py/src/brand_yaml/_defs.py +++ b/pkg-py/src/brand_yaml/_defs.py @@ -36,8 +36,8 @@ def defs_get( level: int = 0, ) -> object: """ - Finds `key` in `deps`, which may require recursively resolving nested - values in `deps`. + Finds `key` in `defs`, which may require recursively resolving nested + values in `defs`. Parameters ---------- From a69c92e65550bfe500c93adc378eb7561f1a7fe1 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 14:58:59 -0400 Subject: [PATCH 117/119] feat(Brand.from_yaml_str): Add method for creating Brand instances from strings --- pkg-py/src/brand_yaml/__init__.py | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 1801418c..3f87d843 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -78,6 +78,54 @@ def from_yaml(cls, path: str | Path): """ return cls.model_validate(read_brand_yaml(path, as_data=True)) + @classmethod + def from_yaml_str(cls, text: str, path: str | Path | None = None): + """ + Create a Brand instance from a YAML string + + Parameters + ---------- + text + The text of the brand YAML file. + path + The optional path on disk for supporting files like logos and fonts. + + Returns + ------- + : + A validated :class:`Brand` object with all fields populated according to + the brand YAML text. + + Raises + ------ + : + Raises `ValueError` or other validation errors from + [pydantic](https://docs.pydantic.dev/latest/) if the brand YAML file + is invalid. + + Examples + -------- + + ```python + from brand_yaml import Brand + + brand = Brand.from_yaml_str(\"\"\" + meta: + name: Brand YAML + color: + primary: "#ff0202" + typography: + base: Open Sans + \"\"\") + ``` + """ + data = yaml.load(text) + + if path is not None: + data["path"] = Path(path).absolute() + + return cls.model_validate(data) + @model_validator(mode="after") def resolve_typography_colors(self): if self.typography is None: From c4e0c542feb62b45dc94aaa03e250ed68190879a Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 16:13:49 -0400 Subject: [PATCH 118/119] fix(BrandTypographyFontPath): Fix font-weight range * Fixes import and validation of font-weight ranges, e.g. "400..800" * Fixes writing of font-family CSS * Adds tests to assert expected behavior --- pkg-py/src/brand_yaml/typography.py | 19 ++++----- .../tests/__snapshots__/test_typography.ambr | 11 +++++ pkg-py/tests/test_typography.py | 40 ++++++++++++++++++- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 0084c95b..3eef6280 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -241,22 +241,17 @@ def model_dump( serialize_as_any: bool = False, ) -> str: ... - @field_validator("root", mode="before") - @classmethod - def validate_root_before(cls, value: Any) -> Any: - if isinstance(value, str) and ".." in value: - value = value.split("..") - return (v for v in value if v) - return value - - @field_validator("root", mode="before") + @model_validator(mode="before") @classmethod - def validate_root( + def validate_root_before( cls, value: Any ) -> ( BrandTypographyFontWeightSimpleAutoType | BrandTypographyFontWeightSimplePairedType ): + if isinstance(value, str) and ".." in value: + value = tuple(value.split("..")) + if isinstance(value, tuple) or isinstance(value, list): if len(value) != 2: raise ValueError( @@ -267,6 +262,7 @@ def validate_root( validate_font_weight(value[1], allow_auto=False), ) return vals + return validate_font_weight(value, allow_auto=True) @@ -317,11 +313,10 @@ class BrandTypographyFontFilesPath(BaseModel): def to_css(self) -> str: # TODO: Handle `file://` vs `https://` or move to correct location - weight = self.weight.to_str_url() src = f"url('{self.path.root}') format('{self.format}')" return "\n".join( [ - f"font-weight: {weight};", + f"font-weight: {self.weight};", f"font-style: {self.style};", f"src: {src};", ] diff --git a/pkg-py/tests/__snapshots__/test_typography.ambr b/pkg-py/tests/__snapshots__/test_typography.ambr index 791a8107..1033f46c 100644 --- a/pkg-py/tests/__snapshots__/test_typography.ambr +++ b/pkg-py/tests/__snapshots__/test_typography.ambr @@ -29,3 +29,14 @@ @import url('https://fonts.bunny.net/css?family=Fira+Code%3A100%2C100i%2C200%2C200i%2C300%2C300i%2C400%2C400i%2C500%2C500i%2C600%2C600i%2C700%2C700i%2C800%2C800i%2C900%2C900i&display=auto'); ''' # --- +# name: test_brand_typography_css_fonts_local + ''' + @font-face { + font-family: 'Open Sans'; + font-weight: 400 800; + font-style: italic; + src: url('OpenSans-Variable.ttf') format('truetype'); + } + @import url('https://fonts.googleapis.com/css2?family=Roboto%3Aital%2Cwght%400%2C200..500%3B1%2C200..500&display=auto'); + ''' +# --- diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index d0de9dbe..f5bd7db2 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -4,7 +4,7 @@ from urllib.parse import unquote import pytest -from brand_yaml import read_brand_yaml +from brand_yaml import Brand, read_brand_yaml from brand_yaml.color import BrandColor from brand_yaml.file import FileLocationLocal from brand_yaml.typography import ( @@ -629,6 +629,44 @@ def test_brand_typography_css_fonts(snapshot): assert snapshot == brand.typography.css_include_fonts() +def test_brand_typography_css_fonts_local(snapshot): + fw = BrandTypographyFontFileWeight.model_validate("400..800") + assert str(fw) == "400 800" + assert fw.model_dump() == "400..800" + assert fw.to_str_url() == "400..800" + + with pytest.raises(ValueError): + BrandTypographyFontFileWeight.model_validate("400..600..900") + + fp = BrandTypographyFontFilesPath.model_validate( + {"path": "OpenSans-Variable.ttf", "weight": "400..800"} + ) + assert fp.to_css() == "\n".join( + [ + "font-weight: 400 800;", + "font-style: normal;", + "src: url('OpenSans-Variable.ttf') format('truetype');", + ] + ) + + brand = Brand.from_yaml_str(""" + typography: + fonts: + - family: Open Sans + source: file + files: + - path: OpenSans-Variable.ttf + weight: 400..800 + style: italic + - family: Roboto + source: google + weight: 200..500 + """) + + assert isinstance(brand.typography, BrandTypography) + assert snapshot == brand.typography.css_include_fonts() + + def test_brand_typography_undefined_colors(): fixtures = Path(__file__).parent / "fixtures" / "typography-undefined-color" From 1415e9cefba0fa232b3161c547c54eb5195c66bc Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Fri, 4 Oct 2024 16:28:26 -0400 Subject: [PATCH 119/119] fix(BrandTypographyGoogleFontsWeightRange): Improve validation and testing --- pkg-py/src/brand_yaml/typography.py | 4 ++-- pkg-py/tests/test_typography.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg-py/src/brand_yaml/typography.py b/pkg-py/src/brand_yaml/typography.py index 3eef6280..d793659c 100644 --- a/pkg-py/src/brand_yaml/typography.py +++ b/pkg-py/src/brand_yaml/typography.py @@ -364,12 +364,12 @@ def __str__(self) -> str: @model_serializer(mode="plain", when_used="always") def to_serialized(self) -> str: - return f"{self.root[0]}..{self.root[1]}" + return str(self) def to_url_list(self) -> list[str]: return [str(self)] - @field_validator("root", mode="before") + @model_validator(mode="before") @classmethod def validate_weight(cls, value: Any) -> list[BrandTypographyFontWeightInt]: if isinstance(value, str) and ".." in value: diff --git a/pkg-py/tests/test_typography.py b/pkg-py/tests/test_typography.py index f5bd7db2..a68bdc06 100644 --- a/pkg-py/tests/test_typography.py +++ b/pkg-py/tests/test_typography.py @@ -667,6 +667,23 @@ def test_brand_typography_css_fonts_local(snapshot): assert snapshot == brand.typography.css_include_fonts() +def test_brand_typography_google_fonts_weight_range(snapshot): + fw = BrandTypographyGoogleFontsWeightRange.model_validate("600..800") + assert fw.root == [600, 800] + assert str(fw) == "600..800" + assert fw.model_dump() == "600..800" + assert fw.to_url_list() == ["600..800"] + + fw = BrandTypographyGoogleFontsWeightRange.model_validate(["thin", "bold"]) + assert fw.root == [100, 700] + + with pytest.raises(ValueError): + BrandTypographyGoogleFontsWeightRange.model_validate("600..800..123") + + with pytest.raises(ValueError): + BrandTypographyGoogleFontsWeightRange.model_validate([200, 400, 600]) + + def test_brand_typography_undefined_colors(): fixtures = Path(__file__).parent / "fixtures" / "typography-undefined-color"