From bbabd013ae98d1f2470019bad04d73ef913f63c2 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 16 Oct 2024 17:05:12 -0400 Subject: [PATCH 1/3] feat(logo): Support alt text via `BrandLogoResource` * Also expose `BrandLogoResource` at top-level * `brand.logo` can be a string, logo resource or `BrandLogo` * improve docs --- docs/pkg/py/logo.qmd | 43 +++++- examples/brand-logo-full-alt.yml | 21 +++ pkg-py/src/brand_yaml/__init__.py | 17 ++- pkg-py/src/brand_yaml/logo.py | 120 ++++++++++++++--- .../test_logo/test_brand_logo_ex_full.json | 40 ++++-- .../test_brand_logo_ex_full_alt.json | 44 ++++++ .../test_brand_logo_ex_light_dark.json | 16 ++- .../test_logo/test_brand_logo_ex_simple.json | 12 +- pkg-py/tests/test_brand.py | 20 +-- pkg-py/tests/test_logo.py | 126 +++++++++++++++--- 10 files changed, 387 insertions(+), 72 deletions(-) create mode 100644 examples/brand-logo-full-alt.yml create mode 100644 pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full_alt.json diff --git a/docs/pkg/py/logo.qmd b/docs/pkg/py/logo.qmd index af4f9ffe..c35df445 100644 --- a/docs/pkg/py/logo.qmd +++ b/docs/pkg/py/logo.qmd @@ -11,16 +11,21 @@ BrandLogo() Brand Logos `logo` stores a single brand logo or a set of logos at three different size -points and possibly in different color schemes. Store all logo or image -assets in `images` with meaningful names. Logos can be specified at three -different sizes -- `small`, `medium`, and `large` -- and each can be either -a single logo file or a light/dark variant (`brand_yaml.BrandLightDark`). +points and possibly in different color schemes. Store all of your brand's +logo or image assets in `images` with meaningful names. Logos can be mapped +to three preset sizes -- `small`, `medium`, and `large` -- and each can be +either a single logo file or a light/dark variant +(`brand_yaml.BrandLightDark`). + +To attach alternative text to an image, provide the image as a dictionary +including `path` (the image location) and `alt` (the short, alternative +text describing the image). ## Attributes {.doc-section .doc-section-attributes} images -: [dict](`dict`)\[[str](`str`), [FileLocationLocalOrUrlType](`brand_yaml.file.FileLocationLocalOrUrlType`)\] \| None +: [dict](`dict`)\[[str](`str`), [BrandLogoResource](`brand_yaml.logo.BrandLogoResource`)\] \| None A dictionary containing any number of logos or brand images. You can refer to these images by their key name in `small`, `medium` or `large`. @@ -109,6 +114,34 @@ logo: large: pandas ``` + + +###### Complete with Alt Text + +```{.yaml filename="_brand.yml"} +logo: + images: + mark: + path: logos/pandas/pandas_mark.svg + alt: pandas logo with blue bars and yellow and pink dots + mark-white: logos/pandas/pandas_mark_white.svg + secondary: logos/pandas/pandas_secondary.svg + secondary-white: + path: logos/pandas/pandas_secondary_white.svg + alt: pandas logo with bars and dots over the word "pandas" + pandas: logos/pandas/pandas.svg + pandas-white: logos/pandas/pandas_white.svg + small: mark + medium: + light: + path: logos/pandas/pandas_secondary.svg + alt: pandas logo with bars and dots over the word "pandas" + dark: secondary-white + large: + path: logos/pandas/pandas.svg + alt: pandas bars and dots to the right of the word "pandas" +``` + ::: # BrandLightDark { #brand_yaml.BrandLightDark } diff --git a/examples/brand-logo-full-alt.yml b/examples/brand-logo-full-alt.yml new file mode 100644 index 00000000..5136364e --- /dev/null +++ b/examples/brand-logo-full-alt.yml @@ -0,0 +1,21 @@ +logo: + images: + mark: + path: logos/pandas/pandas_mark.svg + alt: pandas logo with blue bars and yellow and pink dots + mark-white: logos/pandas/pandas_mark_white.svg + secondary: logos/pandas/pandas_secondary.svg + secondary-white: + path: logos/pandas/pandas_secondary_white.svg + alt: pandas logo with bars and dots over the word "pandas" + pandas: logos/pandas/pandas.svg + pandas-white: logos/pandas/pandas_white.svg + small: mark + medium: + light: + path: logos/pandas/pandas_secondary.svg + alt: pandas logo with bars and dots over the word "pandas" + dark: secondary-white + large: + path: logos/pandas/pandas.svg + alt: pandas bars and dots to the right of the word "pandas" diff --git a/pkg-py/src/brand_yaml/__init__.py b/pkg-py/src/brand_yaml/__init__.py index 1f1bc253..c6741d20 100644 --- a/pkg-py/src/brand_yaml/__init__.py +++ b/pkg-py/src/brand_yaml/__init__.py @@ -17,7 +17,7 @@ from .base import BrandBase from .color import BrandColor from .file import FileLocation, FileLocationLocal, FileLocationUrl -from .logo import BrandLogo +from .logo import BrandLogo, BrandLogoResource from .meta import BrandMeta from .typography import BrandTypography @@ -45,8 +45,9 @@ class Brand(BrandBase): validate_assignment=True, ) + # TODO @docs: Document Brand attributes meta: BrandMeta | None = None - logo: str | BrandLogo | None = None + logo: BrandLogo | BrandLogoResource | None = None color: BrandColor | None = None typography: BrandTypography | None = None defaults: dict[str, Any] | None = None @@ -306,6 +307,17 @@ def _set_root_path(self): return self + @field_validator("logo", mode="before") + @classmethod + def _promote_logo_scalar_to_resource(cls, value: Any): + """ + Take a single path value passed to `brand.logo` and promote it into a + [`brand_yaml.BrandLogoResource`](`brand_yaml.BrandLogoResource`). + """ + if isinstance(value, (str, Path, FileLocation)): + return {"path": value} + return value + @overload def read_brand_yaml( @@ -403,6 +415,7 @@ def read_brand_yaml(path: str | Path, as_data: bool = False) -> Brand | dict: "BrandColor", "BrandTypography", "BrandLightDark", + "BrandLogoResource", "FileLocation", "FileLocationLocal", "FileLocationUrl", diff --git a/pkg-py/src/brand_yaml/logo.py b/pkg-py/src/brand_yaml/logo.py index 43196c04..47b2d219 100644 --- a/pkg-py/src/brand_yaml/logo.py +++ b/pkg-py/src/brand_yaml/logo.py @@ -8,12 +8,14 @@ from __future__ import annotations from pathlib import Path -from typing import Annotated, Any, Union +from typing import Annotated, Any, Literal, Union from pydantic import ( + AnyUrl, ConfigDict, Discriminator, Tag, + field_validator, model_validator, ) @@ -22,22 +24,60 @@ from .base import BrandBase from .file import FileLocation, FileLocationLocalOrUrlType + +class BrandLogoResource(BrandBase): + """A logo resource, a file with optional alternative text""" + + model_config = ConfigDict( + str_strip_whitespace=True, + frozen=True, + extra="forbid", + use_attribute_docstrings=True, + ) + + path: FileLocationLocalOrUrlType + """The path to the logo resource. This can be a local file or a URL.""" + + alt: str | None = None + """Alterative text for the image, used for accessibility.""" + + +def brand_logo_type_discriminator( + x: Any, +) -> Literal["file", "light-dark", "resource"]: + if isinstance(x, dict): + if "path" in x: + return "resource" + if "light" in x or "dark" in x: + return "light-dark" + + if isinstance(x, BrandLightDark): + return "light-dark" + if isinstance(x, BrandLogoResource): + return "resource" + + raise TypeError(f"{type(x)} is not a valid brand logo type") + + +BrandLogoImageType = Union[FileLocationLocalOrUrlType, BrandLogoResource] +""" +A logo image file can be either a local or URL file location, or a dictionary +with `path` and `alt`, the path to the file (local or URL) and an associated +alternative text for the logo image to be used for accessibility. +""" + + BrandLogoFileType = Annotated[ Union[ - Annotated[FileLocationLocalOrUrlType, Tag("file")], - Annotated[ - BrandLightDark[FileLocationLocalOrUrlType], Tag("light-dark") - ], + Annotated[BrandLogoResource, Tag("resource")], + Annotated[BrandLightDark[BrandLogoResource], Tag("light-dark")], ], - Discriminator( - lambda x: "light-dark" - if isinstance(x, (dict, BrandLightDark)) - else "file" - ), + Discriminator(brand_logo_type_discriminator), ] """ -A logo image file can be either a local or URL file location, or a light-dark -variant that includes both a light and dark color scheme. +A logo image file can be either a local or URL file location with optional +alternative text or a light-dark variant that includes both a light and dark +color scheme. """ @@ -46,16 +86,22 @@ {"path": "brand-logo-simple.yml", "name": "Minimal"}, {"path": "brand-logo-light-dark.yml", "name": "Light/Dark Variants"}, {"path": "brand-logo-full.yml", "name": "Complete"}, + {"path": "brand-logo-full-alt.yml", "name": "Complete with Alt Text"}, ) class BrandLogo(BrandBase): """ Brand Logos `logo` stores a single brand logo or a set of logos at three different size - points and possibly in different color schemes. Store all logo or image - assets in `images` with meaningful names. Logos can be specified at three - different sizes -- `small`, `medium`, and `large` -- and each can be either - a single logo file or a light/dark variant (`brand_yaml.BrandLightDark`). + points and possibly in different color schemes. Store all of your brand's + logo or image assets in `images` with meaningful names. Logos can be mapped + to three preset sizes -- `small`, `medium`, and `large` -- and each can be + either a single logo file or a light/dark variant + (`brand_yaml.BrandLightDark`). + + To attach alternative text to an image, provide the image as a dictionary + including `path` (the image location) and `alt` (the short, alternative + text describing the image). Attributes ---------- @@ -87,7 +133,7 @@ class BrandLogo(BrandBase): model_config = ConfigDict(extra="forbid") - images: dict[str, FileLocationLocalOrUrlType] | None = None + images: dict[str, BrandLogoResource] | None = None small: BrandLogoFileType | None = None medium: BrandLogoFileType | None = None large: BrandLogoFileType | None = None @@ -109,9 +155,47 @@ def _resolve_image_values(cls, data: Any): raise ValueError("images must be a dictionary of file locations") for key, value in images.items(): + if isinstance(value, dict): + # pydantic will handle validation of dict values + continue + 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") + # Promote bare file locations to BrandLogoResource locations + images[key] = {"path": value} + + defs_replace_recursively(data, defs=images, name="logo", exclude="path") return data + + @field_validator("small", "medium", "large", mode="before") + @classmethod + def _promote_bare_files_to_logo_resource(cls, value: Any): + """ + Takes any bare file location references and promotes them to the + structure required for BrandLogoResource. + + This results in a more nested but consistent data structure where each + image is always a `BrandLogoResource` instance that's guaranteed to have + a `path` item and optionally may include `alt` text. + """ + if isinstance(value, (str, Path, AnyUrl)): + # Bare strings/paths become BrandLogoResource without `alt` + return {"path": value} + + if isinstance(value, dict): + for k in ("light", "dark"): + if k not in value: + continue + value[k] = cls._promote_bare_files_to_logo_resource(value[k]) + + if isinstance(value, BrandLightDark): + for k in ("light", "dark"): + prop = getattr(value, k) + if prop is not None: + setattr( + value, k, cls._promote_bare_files_to_logo_resource(prop) + ) + + return value 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 b8c304af..709fd06f 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,18 +1,38 @@ { "logo": { "images": { - "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" + "mark": { + "path": "logos/pandas/pandas_mark.svg" + }, + "mark-white": { + "path": "logos/pandas/pandas_mark_white.svg" + }, + "pandas": { + "path": "logos/pandas/pandas.svg" + }, + "pandas-white": { + "path": "logos/pandas/pandas_white.svg" + }, + "secondary": { + "path": "logos/pandas/pandas_secondary.svg" + }, + "secondary-white": { + "path": "logos/pandas/pandas_secondary_white.svg" + } + }, + "large": { + "path": "logos/pandas/pandas.svg" }, - "large": "logos/pandas/pandas.svg", "medium": { - "dark": "logos/pandas/pandas_secondary_white.svg", - "light": "logos/pandas/pandas_secondary.svg" + "dark": { + "path": "logos/pandas/pandas_secondary_white.svg" + }, + "light": { + "path": "logos/pandas/pandas_secondary.svg" + } }, - "small": "logos/pandas/pandas_mark.svg" + "small": { + "path": "logos/pandas/pandas_mark.svg" + } } } diff --git a/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full_alt.json b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full_alt.json new file mode 100644 index 00000000..230f522b --- /dev/null +++ b/pkg-py/tests/__snapshots__/test_logo/test_brand_logo_ex_full_alt.json @@ -0,0 +1,44 @@ +{ + "logo": { + "images": { + "mark": { + "alt": "pandas logo with blue bars and yellow and pink dots", + "path": "logos/pandas/pandas_mark.svg" + }, + "mark-white": { + "path": "logos/pandas/pandas_mark_white.svg" + }, + "pandas": { + "path": "logos/pandas/pandas.svg" + }, + "pandas-white": { + "path": "logos/pandas/pandas_white.svg" + }, + "secondary": { + "path": "logos/pandas/pandas_secondary.svg" + }, + "secondary-white": { + "alt": "pandas logo with bars and dots over the word \"pandas\"", + "path": "logos/pandas/pandas_secondary_white.svg" + } + }, + "large": { + "alt": "pandas bars and dots to the right of the word \"pandas\"", + "path": "logos/pandas/pandas.svg" + }, + "medium": { + "dark": { + "alt": "pandas logo with bars and dots over the word \"pandas\"", + "path": "logos/pandas/pandas_secondary_white.svg" + }, + "light": { + "alt": "pandas logo with bars and dots over the word \"pandas\"", + "path": "logos/pandas/pandas_secondary.svg" + } + }, + "small": { + "alt": "pandas logo with blue bars and yellow and pink dots", + "path": "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 80eb5216..8621e9d2 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,18 @@ { "logo": { - "large": "logos/pandas/pandas.svg", + "large": { + "path": "logos/pandas/pandas.svg" + }, "medium": { - "dark": "logos/pandas/pandas_secondary_white.svg", - "light": "logos/pandas/pandas_secondary.svg" + "dark": { + "path": "logos/pandas/pandas_secondary_white.svg" + }, + "light": { + "path": "logos/pandas/pandas_secondary.svg" + } }, - "small": "logos/pandas/pandas_mark.svg" + "small": { + "path": "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 a64eba06..c729b5b4 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,13 @@ { "logo": { - "large": "logos/pandas/pandas.svg", - "medium": "logos/pandas/pandas_secondary.svg", - "small": "logos/pandas/pandas_mark.svg" + "large": { + "path": "logos/pandas/pandas.svg" + }, + "medium": { + "path": "logos/pandas/pandas_secondary.svg" + }, + "small": { + "path": "logos/pandas/pandas_mark.svg" + } } } diff --git a/pkg-py/tests/test_brand.py b/pkg-py/tests/test_brand.py index 9a4883ce..e5dd8cdf 100644 --- a/pkg-py/tests/test_brand.py +++ b/pkg-py/tests/test_brand.py @@ -4,7 +4,7 @@ import pytest from brand_yaml import Brand, read_brand_yaml from brand_yaml.file import FileLocationLocal -from brand_yaml.logo import BrandLogo +from brand_yaml.logo import BrandLogo, BrandLogoResource from brand_yaml.typography import BrandTypography, BrandTypographyFontFiles path_fixtures = Path(__file__).parent / "fixtures" @@ -51,7 +51,8 @@ def test_brand_yml_paths(): brand = read_brand_yaml(path) assert isinstance(brand.logo, BrandLogo) - assert isinstance(brand.logo.small, FileLocationLocal) + assert isinstance(brand.logo.small, BrandLogoResource) + assert isinstance(brand.logo.small.path, FileLocationLocal) assert isinstance(brand.typography, BrandTypography) assert isinstance(brand.typography.fonts, list) @@ -61,8 +62,8 @@ def test_brand_yml_paths(): ) # Paths are all relative - assert brand.logo.small.root == Path("does-not-exist.png") - assert brand.logo.small.root == brand.logo.small.relative() + assert brand.logo.small.path.root == Path("does-not-exist.png") + assert brand.logo.small.path.root == brand.logo.small.path.relative() assert brand.typography.fonts[0].files[0].path.root == Path("Invisible.ttf") assert ( @@ -72,7 +73,8 @@ def test_brand_yml_paths(): # Paths can be accessed absolutely assert ( - brand.logo.small.absolute() == (path / "does-not-exist.png").absolute() + brand.logo.small.path.absolute() + == (path / "does-not-exist.png").absolute() ) assert ( brand.typography.fonts[0].files[0].path.absolute() @@ -80,15 +82,15 @@ def test_brand_yml_paths(): ) # These files don't exist, which can be verified - assert not brand.logo.small.exists() + assert not brand.logo.small.path.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.logo.small.path.root == Path("does-not-exist.png") + assert brand.logo.small.path.root == brand.logo.small.path.relative() assert brand.typography.fonts[0].files[0].path.root == Path("Invisible.ttf") assert ( @@ -98,7 +100,7 @@ def test_brand_yml_paths(): # Absolute paths have now been updated assert ( - brand.logo.small.absolute() + brand.logo.small.path.absolute() == (path_fixtures / "does-not-exist.png").absolute() ) assert ( diff --git a/pkg-py/tests/test_logo.py b/pkg-py/tests/test_logo.py index 92cccc75..5d7f27f7 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -3,10 +3,10 @@ from pathlib import Path import pytest -from brand_yaml import read_brand_yaml +from brand_yaml import Brand, read_brand_yaml from brand_yaml._defs import BrandLightDark from brand_yaml.file import FileLocation, FileLocationLocal -from brand_yaml.logo import BrandLogo +from brand_yaml.logo import BrandLogo, BrandLogoResource from syrupy.extensions.json import JSONSnapshotExtension from utils import path_examples, pydantic_data_from_json @@ -19,7 +19,9 @@ def snapshot_json(snapshot): def test_brand_logo_single(): brand = read_brand_yaml(path_examples("brand-logo-single.yml")) - assert brand.logo == "posit.png" + assert isinstance(brand.logo, BrandLogoResource) + assert isinstance(brand.logo.path, FileLocationLocal) + assert str(brand.logo.path) == "posit.png" def test_brand_logo_errors(): @@ -42,14 +44,17 @@ def test_brand_logo_ex_simple(snapshot_json): assert isinstance(brand.logo, BrandLogo) - assert isinstance(brand.logo.small, FileLocation) - assert str(brand.logo.small) == "logos/pandas/pandas_mark.svg" + assert isinstance(brand.logo.small, BrandLogoResource) + assert isinstance(brand.logo.small.path, FileLocation) + assert str(brand.logo.small.path) == "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.medium, BrandLogoResource) + assert isinstance(brand.logo.medium.path, FileLocation) + assert str(brand.logo.medium.path) == "logos/pandas/pandas_secondary.svg" - assert isinstance(brand.logo.large, FileLocation) - assert str(brand.logo.large) == "logos/pandas/pandas.svg" + assert isinstance(brand.logo.large, BrandLogoResource) + assert isinstance(brand.logo.large.path, FileLocation) + assert str(brand.logo.large.path) == "logos/pandas/pandas.svg" assert snapshot_json == pydantic_data_from_json(brand) @@ -58,19 +63,26 @@ 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, FileLocationLocal) - assert str(brand.logo.small) == "logos/pandas/pandas_mark.svg" + assert isinstance(brand.logo.small, BrandLogoResource) + assert isinstance(brand.logo.small.path, FileLocationLocal) + assert str(brand.logo.small.path) == "logos/pandas/pandas_mark.svg" assert isinstance(brand.logo.medium, BrandLightDark) - assert isinstance(brand.logo.medium.light, FileLocationLocal) - assert str(brand.logo.medium.light) == "logos/pandas/pandas_secondary.svg" - assert isinstance(brand.logo.medium.dark, FileLocationLocal) + assert isinstance(brand.logo.medium.light, BrandLogoResource) + assert isinstance(brand.logo.medium.light.path, FileLocationLocal) assert ( - str(brand.logo.medium.dark) == "logos/pandas/pandas_secondary_white.svg" + str(brand.logo.medium.light.path) == "logos/pandas/pandas_secondary.svg" + ) + assert isinstance(brand.logo.medium.dark, BrandLogoResource) + assert isinstance(brand.logo.medium.dark.path, FileLocationLocal) + assert ( + str(brand.logo.medium.dark.path) + == "logos/pandas/pandas_secondary_white.svg" ) - assert isinstance(brand.logo.large, FileLocationLocal) - assert str(brand.logo.large) == "logos/pandas/pandas.svg" + assert isinstance(brand.logo.large, BrandLogoResource) + assert isinstance(brand.logo.large.path, FileLocationLocal) + assert str(brand.logo.large.path) == "logos/pandas/pandas.svg" assert snapshot_json == pydantic_data_from_json(brand) @@ -80,17 +92,21 @@ def test_brand_logo_ex_full(snapshot_json): assert isinstance(brand.logo, BrandLogo) assert isinstance(brand.logo.images, dict) - assert isinstance(brand.logo.small, FileLocationLocal) + assert isinstance(brand.logo.small, BrandLogoResource) + assert isinstance(brand.logo.small.path, FileLocationLocal) assert brand.logo.small == brand.logo.images["mark"] assert isinstance(brand.logo.medium, BrandLightDark) - assert isinstance(brand.logo.medium.light, FileLocationLocal) - assert brand.logo.medium.light.root == Path( + assert isinstance(brand.logo.medium.light, BrandLogoResource) + assert isinstance(brand.logo.medium.light.path, FileLocationLocal) + assert brand.logo.medium.light.path.root == Path( "logos/pandas/pandas_secondary.svg" ) + assert isinstance(brand.logo.medium.dark, BrandLogoResource) assert brand.logo.medium.dark == brand.logo.images["secondary-white"] - assert isinstance(brand.logo.large, FileLocationLocal) + assert isinstance(brand.logo.large, BrandLogoResource) + assert isinstance(brand.logo.large.path, FileLocationLocal) assert brand.logo.large == brand.logo.images["pandas"] ## THIS IS NOT CURRENTLY SUPPORTED @@ -104,3 +120,71 @@ def test_brand_logo_ex_full(snapshot_json): # assert brand.logo.small == brand.logo.images["mark-white"] assert snapshot_json == pydantic_data_from_json(brand) + + +def test_brand_logo_resource_images_simple(): + brand = Brand.from_yaml_str(""" + logo: + images: + logo: brand-yaml.png + small: logo + """) + + # logo.images.* are promoted to BrandLogoResource + assert isinstance(brand.logo, BrandLogo) + assert isinstance(brand.logo.images, dict) + assert "logo" in brand.logo.images + assert isinstance(brand.logo.images["logo"], BrandLogoResource) + assert isinstance(brand.logo.images["logo"].path, FileLocationLocal) + assert brand.logo.images["logo"].alt is None + assert str(brand.logo.images["logo"].path.relative()) == "brand-yaml.png" + + # and are used directly by logo.* + assert isinstance(brand.logo.small, BrandLogoResource) + assert brand.logo.small == brand.logo.images["logo"] + + +def test_brand_logo_resource_images_with_alt(): + brand = Brand.from_yaml_str(""" + logo: + images: + logo: + path: brand-yaml.png + alt: "Brand YAML Logo" + small: logo + """) + + assert isinstance(brand.logo, BrandLogo) + assert isinstance(brand.logo.images, dict) + assert "logo" in brand.logo.images + assert isinstance(brand.logo.images["logo"], BrandLogoResource) + assert isinstance(brand.logo.images["logo"].path, FileLocationLocal) + assert isinstance(brand.logo.images["logo"].alt, str) + assert str(brand.logo.images["logo"].path.relative()) == "brand-yaml.png" + + # and are used directly by logo.* + assert isinstance(brand.logo.small, BrandLogoResource) + assert brand.logo.small == brand.logo.images["logo"] + assert brand.logo.small.alt == "Brand YAML Logo" + + +def test_brand_logo_resource_direct_with_alt(): + brand = Brand.from_yaml_str(""" + logo: + small: + path: brand-yaml.png + alt: "Brand YAML Logo" + """) + + assert isinstance(brand.logo, BrandLogo) + + # and are used directly by logo.* + assert isinstance(brand.logo.small, BrandLogoResource) + assert str(brand.logo.small.path) == "brand-yaml.png" + assert brand.logo.small.alt == "Brand YAML Logo" + + +def test_brand_logo_ex_full_alt(snapshot_json): + brand = read_brand_yaml(path_examples("brand-logo-full-alt.yml")) + + assert snapshot_json == pydantic_data_from_json(brand) From be3c5657670e892161d1480e58f84cd900930986 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 16 Oct 2024 17:05:38 -0400 Subject: [PATCH 2/3] chore: make py-docs --- docs/pkg/py/typography.qmd | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/pkg/py/typography.qmd b/docs/pkg/py/typography.qmd index 4c7b8093..4026d7aa 100644 --- a/docs/pkg/py/typography.qmd +++ b/docs/pkg/py/typography.qmd @@ -156,8 +156,6 @@ color: background: '#f7f4f4' typography: - base: - color: foreground headings: color: primary monospace-inline: From ff4db15c30e8b0b8276593147acf7a32edce4e82 Mon Sep 17 00:00:00 2001 From: Garrick Aden-Buie Date: Wed, 16 Oct 2024 17:12:52 -0400 Subject: [PATCH 3/3] chore: fix formatting --- 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 5d7f27f7..4a923214 100644 --- a/pkg-py/tests/test_logo.py +++ b/pkg-py/tests/test_logo.py @@ -148,7 +148,7 @@ def test_brand_logo_resource_images_with_alt(): brand = Brand.from_yaml_str(""" logo: images: - logo: + logo: path: brand-yaml.png alt: "Brand YAML Logo" small: logo