diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..823633f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.fountain] +trim_trailing_whitespace = false diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..20c0837 --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,27 @@ +name: Pre-commit checks + +on: + push: + branches: [ "master" ] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.12", "3.13"] + + steps: + - uses: actions/checkout@v6 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install uv + uses: astral-sh/setup-uv@v7 + - name: Install setup uv + run: uv sync --frozen --dev + - name: Run pre-commit + uses: j178/prek-action@v1 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index d2db3e4..eb4d718 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -1,10 +1,9 @@ -name: Python package +name: Python build and tests on: push: branches: [ "master" ] pull_request: - branches: [ "master" ] jobs: build: @@ -12,19 +11,20 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.13"] + python-version: ["3.12", "3.13"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - cache: "pip" - name: Install uv - run: python -m pip install uv + uses: astral-sh/setup-uv@v7 + - name: Install setup uv + run: uv sync --frozen --dev - name: Test - run: bin/test + run: uv run pytest --doctest-modules -W error - name: Command-line smoke test run: | uv run screenplain tests/files/simple.fountain /tmp/simple.pdf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..bd61969 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,29 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: check-added-large-files + - id: trailing-whitespace + exclude: \.fountain$ + - id: no-commit-to-branch + args: [--branch, main] + - repo: local + hooks: + - id: ruff-format + name: ruff format + language: system + entry: uv run ruff format + pass_filenames: false + files: "\\.py$" + - id: ruff-check + name: ruff check + language: system + entry: uv run ruff check --fix + pass_filenames: false + files: "\\.py$" + - id: ty + name: ty + language: system + entry: uv run --no-sync ty check + files: "\\.pyi?$" + pass_filenames: false \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 1b961d8..c7e8894 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "screenplain" version = "0.12.0" description = "Convert text file to viewable screenplay." readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.12" license = "MIT" authors = [ {name = "Martin Vilcans", email = "screenplain@librador.com"} @@ -41,7 +41,8 @@ dev = [ ] [tool.ruff] -src = ["./"] +src = ["."] +include = ["*.py"] [tool.ruff.lint] select = [ diff --git a/screenplain/export/fdx.py b/screenplain/export/fdx.py index 6ca5081..189b2fe 100644 --- a/screenplain/export/fdx.py +++ b/screenplain/export/fdx.py @@ -2,25 +2,27 @@ # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php +from typing import TextIO from xml.sax.saxutils import escape -from screenplain.richstring import Bold, Italic, Underline +from screenplain.richstring import Bold, Italic, RichString, Style, Underline from screenplain.types import ( Action, Dialog, DualDialog, + Screenplay, Slug, Transition, ) -style_names = { +style_names: dict[type[Style], str] = { Bold: "Bold", Italic: "Italic", Underline: "Underline", } -def _write_text_element(out, styles, text): +def _write_text_element(out: TextIO, styles: list[str], text: str) -> None: style_value = "+".join(str(s) for s in styles) if style_value == "": out.write(f" {escape(text)}\n") @@ -28,7 +30,7 @@ def _write_text_element(out, styles, text): out.write(f' {escape(text)}\n') -def write_text(out, rich, trailing_linebreak) -> None: +def write_text(out: TextIO, rich: RichString, trailing_linebreak: bool) -> None: """Writes elements.""" for seg_no, segment in enumerate(rich.segments): fdx_styles = [style_names[n] for n in segment.get_ordered_styles()] @@ -38,7 +40,12 @@ def write_text(out, rich, trailing_linebreak) -> None: _write_text_element(out, fdx_styles, segment.text) -def write_paragraph(out, para_type, lines, centered=False) -> None: +def write_paragraph( + out: TextIO, + para_type: str, + lines: list[RichString], + centered: bool = False, +) -> None: if centered: out.write(f' \n') else: @@ -50,7 +57,7 @@ def write_paragraph(out, para_type, lines, centered=False) -> None: out.write(" \n") -def write_dialog(out, dialog) -> None: +def write_dialog(out: TextIO, dialog: Dialog) -> None: write_paragraph(out, "Character", [dialog.character]) for parenthetical, line in dialog.blocks: if parenthetical: @@ -59,14 +66,14 @@ def write_dialog(out, dialog) -> None: write_paragraph(out, "Dialogue", [line]) -def write_dual_dialog(out, dual) -> None: +def write_dual_dialog(out: TextIO, dual: DualDialog) -> None: out.write(" \n \n") write_dialog(out, dual.left) write_dialog(out, dual.right) out.write(" \n \n") -def to_fdx(screenplay, out) -> None: +def to_fdx(screenplay: Screenplay, out: TextIO) -> None: out.write( '\n' '\n' diff --git a/screenplain/export/html.py b/screenplain/export/html.py index 3268b15..aed2c38 100644 --- a/screenplain/export/html.py +++ b/screenplain/export/html.py @@ -2,15 +2,20 @@ # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php +from __future__ import annotations + import os import os.path +from collections.abc import Callable +from typing import TextIO -from screenplain.richstring import plain +from screenplain.richstring import RichString, plain from screenplain.types import ( Action, Dialog, DualDialog, PageBreak, + Screenplay, Section, Slug, Transition, @@ -43,24 +48,29 @@ class tag: """ - def __init__(self, out, tag, classes=None): + def __init__( + self, out: TextIO, tag: str, classes: list[str] | set[str] | None = None + ) -> None: self.out = out self.tag = tag self.classes = classes - def __enter__(self): + def __enter__(self) -> tag: if self.classes: self.out.write(f'<{self.tag} class="{" ".join(self.classes)}">') else: self.out.write(f"<{self.tag}>") + return self - def __exit__(self, exception_type, value, traceback): + def __exit__( + self, exception_type: object, value: object, traceback: object + ) -> bool: if not exception_type: self.out.write(f"") return False -def to_html(text): +def to_html(text: RichString) -> str: html = text.to_html() if html == "": return " " @@ -71,7 +81,7 @@ def to_html(text): class Formatter: """Class for converting paragraphs into HTML.""" - def __init__(self, out): + def __init__(self, out: TextIO) -> None: """Initializes the formatter. `out` is a file-like object to write to. @@ -80,7 +90,7 @@ def __init__(self, out): """ self.out = out - self._format_functions = { + self._format_functions: dict[type, Callable[..., None]] = { Slug: self.format_slug, Action: self.format_action, Dialog: self.format_dialog, @@ -90,7 +100,7 @@ def __init__(self, out): PageBreak: self.format_page_break, } - def convert(self, screenplay) -> None: + def convert(self, screenplay: Screenplay) -> None: """Converts a number of paragraphs into HTML and writes it to the output stream. `screenplay` is a sequence of paragraphs. @@ -103,11 +113,11 @@ def convert(self, screenplay) -> None: format_function(para) self.out.write("\n") - def format_dialog(self, dialog) -> None: + def format_dialog(self, dialog: Dialog) -> None: with self._tag("div", classes=["dialog"]): self._write_dialog_block(dialog) - def format_dual(self, dual) -> None: + def format_dual(self, dual: DualDialog) -> None: with self._tag("div", classes=["dual"]): with self._tag("div", classes=["left"]): self._write_dialog_block(dual.left) @@ -115,7 +125,7 @@ def format_dual(self, dual) -> None: self._write_dialog_block(dual.right) self.out.write("
") - def _write_dialog_block(self, dialog): + def _write_dialog_block(self, dialog: Dialog) -> None: with self._tag("p", classes=["character"]): self.out.write(to_html(dialog.character)) @@ -124,28 +134,28 @@ def _write_dialog_block(self, dialog): with self._tag("p", classes=classes): self.out.write(to_html(text)) - def format_slug(self, slug) -> None: + def format_slug(self, slug: Slug) -> None: num = slug.scene_number with self._tag("h6"): if num: with self._tag("span", classes=["scnuml"]): - self.out.write(to_html(slug.scene_number)) + self.out.write(to_html(num)) self.out.write(to_html(slug.line)) if num: with self._tag("span", classes=["scnumr"]): - self.out.write(to_html(slug.scene_number)) + self.out.write(to_html(num)) if slug.synopsis: with self._tag("span", classes=["h6-synopsis"]): self.out.write(to_html(plain(slug.synopsis))) - def format_section(self, section) -> None: + def format_section(self, section: Section) -> None: with self._tag(f"h{section.level}"): self.out.write(to_html(section.text)) if section.synopsis: with self._tag("span", classes=[f"h{section.level}-synopsis"]): self.out.write(to_html(plain(section.synopsis))) - def format_action(self, para) -> None: + def format_action(self, para: Action) -> None: classes = ["action"] if para.centered: classes.append("centered") @@ -156,23 +166,27 @@ def format_action(self, para) -> None: self.out.write("
") self.out.write(to_html(line)) - def format_transition(self, para) -> None: + def format_transition(self, para: Transition) -> None: with self._tag("div", classes=["transition"]): self.out.write(to_html(para.line)) - def format_page_break(self, para) -> None: + def format_page_break(self, para: PageBreak) -> None: self.page_break_before_next = True - def _tag(self, tag_name, classes=None): - if classes is None: - classes = [] + def _tag(self, tag_name: str, classes: list[str] | None = None) -> tag: + tag_classes: list[str] | set[str] = classes if classes is not None else [] if self.page_break_before_next: self.page_break_before_next = False - classes = set(classes).union(("page-break",)) - return tag(self.out, tag_name, classes) + tag_classes = set(tag_classes).union(("page-break",)) + return tag(self.out, tag_name, tag_classes) -def convert(screenplay, out, css_file=None, bare=False) -> None: +def convert( + screenplay: Screenplay, + out: TextIO, + css_file: str | None = None, + bare: bool = False, +) -> None: """Convert the screenplay into HTML, written to the file-like object `out`. The output will be a complete HTML document unless `bare` is true. @@ -188,7 +202,7 @@ def convert(screenplay, out, css_file=None, bare=False) -> None: ) -def convert_full(screenplay, out, css_file) -> None: +def convert_full(screenplay: Screenplay, out: TextIO, css_file: str) -> None: """Convert the screenplay into a complete HTML document, written to the file-like object `out`. @@ -204,7 +218,7 @@ def convert_full(screenplay, out, css_file) -> None: out.write("\n") -def convert_bare(screenplay, out) -> None: +def convert_bare(screenplay: Screenplay, out: TextIO) -> None: """Convert the screenplay into HTML, written to the file-like object `out`. Does not create a complete HTML document, as it doesn't include , , etc. diff --git a/screenplain/export/pdf.py b/screenplain/export/pdf.py index 30cc35a..55bbd0f 100644 --- a/screenplain/export/pdf.py +++ b/screenplain/export/pdf.py @@ -5,6 +5,8 @@ import os import re import sys +from io import TextIOWrapper +from typing import override from reportlab import platypus from reportlab.lib import colors, pagesizes @@ -24,7 +26,15 @@ ) from screenplain import types -from screenplain.types import Action, Dialog, DualDialog, Slug, Transition +from screenplain.richstring import RichString +from screenplain.types import ( + Action, + Dialog, + DualDialog, + Screenplay, + Slug, + Transition, +) try: import reportlab @@ -41,7 +51,7 @@ class FontSettings: file_italic: str | None file_bold_italic: str | None - def __init__(self, family_name): + def __init__(self, family_name: str) -> None: self.family_name = family_name self.file_normal = None self.file_bold = None @@ -84,10 +94,6 @@ def get_courier_prime_settings() -> FontSettings: return s -def create_default_settings(): - return Settings() - - class Settings: # General styles default_style: ParagraphStyle @@ -109,8 +115,8 @@ class Settings: title_style: ParagraphStyle contact_style: ParagraphStyle - font_size: int - line_height: int + font_size: float + line_height: float character_width: float lines_per_page: int characters_per_line: int @@ -127,14 +133,14 @@ class Settings: def __init__( self, - font_size=12, - line_height=None, - lines_per_page=55, - characters_per_line=61, - page_size=pagesizes.letter, - strong_slugs=False, - font_settings=None, - ): + font_size: float = 12, + line_height: float | None = None, + lines_per_page: int = 55, + characters_per_line: int = 61, + page_size: tuple[float, float] = pagesizes.letter, + strong_slugs: bool = False, + font_settings: FontSettings | None = None, + ) -> None: self.font_settings = font_settings or get_courier_prime_settings() line_height = line_height or font_size @@ -241,10 +247,19 @@ def __init__( ) +def create_default_settings() -> Settings: + return Settings() + + class SlugWithSceneNumbers(Flowable): """Custom flowable that renders a slug with scene numbers in margins.""" - def __init__(self, slug_paragraph, scene_number, settings: Settings): + def __init__( + self, + slug_paragraph: Paragraph, + scene_number: RichString, + settings: Settings, + ) -> None: Flowable.__init__(self) self.slug_paragraph = slug_paragraph self.scene_number = scene_number.to_html() @@ -254,7 +269,8 @@ def __init__(self, slug_paragraph, scene_number, settings: Settings): self.spaceAfter = slug_paragraph.style.spaceAfter self.keepWithNext = slug_paragraph.style.keepWithNext - def wrap(self, availWidth, availHeight): + @override + def wrap(self, availWidth: float, availHeight: float) -> tuple[float, float]: # Delegate to the wrapped paragraph return self.slug_paragraph.wrap(availWidth, availHeight) @@ -280,9 +296,12 @@ def draw(self) -> None: class DocTemplate(BaseDocTemplate): - def __init__(self, *args, **kwargs): - self.settings = kwargs.pop("settings", None) or create_default_settings() - self.has_title_page = kwargs.pop("has_title_page", False) + def __init__(self, *args: object, **kwargs: object) -> None: + settings = kwargs.pop("settings", None) + self.settings: Settings = ( + settings if isinstance(settings, Settings) else create_default_settings() + ) + self.has_title_page: bool = bool(kwargs.pop("has_title_page", False)) frame = Frame( self.settings.left_margin, self.settings.bottom_margin, @@ -309,8 +328,9 @@ def __init__(self, *args, **kwargs): PageTemplate(id="title", frames=[title_frame]), PageTemplate(id="standard", frames=[frame]), ] - BaseDocTemplate.__init__(self, pageTemplates=pageTemplates, *args, **kwargs) + BaseDocTemplate.__init__(self, *args, pageTemplates=pageTemplates, **kwargs) + @override def handle_pageBegin(self) -> None: self.canv.setFont( self.settings.font_settings.family_name, @@ -330,11 +350,13 @@ def handle_pageBegin(self) -> None: self._handle_pageBegin() -def add_paragraph(story, para, style) -> None: +def add_paragraph( + story: list[Flowable], para: Action | Transition, style: ParagraphStyle +) -> None: story.append(Paragraph("
".join(line.to_html() for line in para.lines), style)) -def add_slug(story, para, settings) -> None: +def add_slug(story: list[Flowable], para: Slug, settings: Settings) -> None: for line in para.lines: if settings.strong_slugs: html = "" + line.to_html() + "" @@ -352,15 +374,15 @@ def add_slug(story, para, settings) -> None: def _dialog_to_flowables( - dialog, settings: Settings, column_width=None + dialog: Dialog, settings: Settings, column_width: float | None = None ) -> list[Flowable]: # If column_width is set, adjust indents proportionally if column_width is not None: proportion = column_width / settings.frame_width - character_left = settings.character_style.leftIndent * proportion - dialog_left = settings.dialog_style.leftIndent * proportion - parenthentical_left = settings.parenthentical_style.leftIndent * proportion - dialog_right = settings.dialog_style.rightIndent * proportion + character_left = settings.character_style.leftIndent * proportion # ty: ignore[unresolved-attribute] + dialog_left = settings.dialog_style.leftIndent * proportion # ty: ignore[unresolved-attribute] + parenthentical_left = settings.parenthentical_style.leftIndent * proportion # ty: ignore[unresolved-attribute] + dialog_right = settings.dialog_style.rightIndent * proportion # ty: ignore[unresolved-attribute] character_style = ParagraphStyle( "character-dual", settings.character_style, leftIndent=character_left ) @@ -394,13 +416,15 @@ def _dialog_to_flowables( return flowables -def add_dialog(story, dialog, settings: Settings) -> None: +def add_dialog(story: list[Flowable], dialog: Dialog, settings: Settings) -> None: flowables = _dialog_to_flowables(dialog, settings) for flowable in flowables: story.append(flowable) -def add_dual_dialog(story, dual, settings: Settings) -> None: +def add_dual_dialog( + story: list[Flowable], dual: DualDialog, settings: Settings +) -> None: # Format dual dialog side-by-side using a Table col_width = settings.frame_width / 2 left_flowables = _dialog_to_flowables(dual.left, settings, column_width=col_width) @@ -418,15 +442,21 @@ def add_dual_dialog(story, dual, settings: Settings) -> None: story.append(table) -def get_title_page_story(screenplay, settings): +def get_title_page_story(screenplay: Screenplay, settings: Settings) -> list[Flowable]: """Get Platypus flowables for the title page""" + # From Fountain spec: # The recommendation is that Title, Credit, Author (or Authors, either # is a valid key syntax), and Source will be centered on the page in # formatted output. Contact and Draft date would be placed at the lower # left. - def add_lines(story, attribute, style, space_before=0): + def add_lines( + story: list[Flowable], + attribute: str, + style: ParagraphStyle, + space_before: float = 0, + ) -> float: lines = screenplay.get_rich_attribute(attribute) if not lines: return 0 @@ -481,7 +511,7 @@ def add_lines(story, attribute, style, space_before=0): if not title_story and not lower_story: return [] - story = [] + story: list[Flowable] = [] top_space = min( settings.frame_height / 3.0, settings.frame_height - lower_height - title_height ) @@ -502,7 +532,7 @@ def add_lines(story, attribute, style, space_before=0): return story -def pdf_metadata(screenplay): +def pdf_metadata(screenplay: Screenplay) -> dict[str, str | list[str] | None]: title_lines = screenplay.get_rich_attribute("Title") author_lines = [ *screenplay.get_rich_attribute("Author"), @@ -531,7 +561,10 @@ def pdf_metadata(screenplay): def to_pdf( - screenplay, output_filename, template_constructor=DocTemplate, settings=None + screenplay: Screenplay, + output_filename: TextIOWrapper, + template_constructor: type[DocTemplate] = DocTemplate, + settings: Settings | None = None, ) -> None: settings = settings or create_default_settings() story = get_title_page_story(screenplay, settings) diff --git a/screenplain/main.py b/screenplain/main.py index 63d5ae3..72234ed 100644 --- a/screenplain/main.py +++ b/screenplain/main.py @@ -21,14 +21,14 @@ an output-file is given. Otherwise use the --format option.""" -def invalid_format(parser, message) -> None: +def invalid_format(parser: argparse.ArgumentParser, message: str) -> None: formats = " ".join(output_formats) parser.error( f"{message}\nUse --format with one of the following formats: {formats}" ) -def main(argv) -> None: +def main(argv: list[str]) -> None: parser = argparse.ArgumentParser( description=description, formatter_class=argparse.RawDescriptionHelpFormatter ) @@ -104,39 +104,39 @@ def main(argv) -> None: except LookupError: parser.error(f"Unknown encoding: {args.encoding}") - format = args.output_format - if format is None and output_file: + out_format = args.output_format + if out_format is None and output_file: if output_file.endswith(".fdx"): - format = "fdx" + out_format = "fdx" elif output_file.endswith(".html"): - format = "html" + out_format = "html" elif output_file.endswith(".pdf"): - format = "pdf" + out_format = "pdf" else: invalid_format( parser, f"Could not detect output format from file name {output_file}" ) - if format not in output_formats: - invalid_format(parser, f'Unsupported output format: "{format}".') + if out_format not in output_formats: + invalid_format(parser, f'Unsupported output format: "{out_format}".') if input_file: - input = codecs.open( - input_file, "r", encoding=args.encoding, errors=args.encoding_errors + input_stream = open( + input_file, encoding=args.encoding, errors=args.encoding_errors ) else: - input = codecs.getreader(args.encoding)(sys.stdin.buffer) - input.errors = args.encoding_errors - screenplay = fountain.parse(input) + input_stream = codecs.getreader(args.encoding)(sys.stdin.buffer) + input_stream.errors = args.encoding_errors + screenplay = fountain.parse(input_stream) # type: ignore[arg-type] # ty: ignore[invalid-argument-type] - if format == "pdf": + if out_format == "pdf": output_encoding = None else: output_encoding = "utf-8" if output_file: if output_encoding: - output = codecs.open(output_file, "w", output_encoding) + output = open(output_file, mode="w", encoding=output_encoding) else: output = open(output_file, "wb") else: @@ -146,15 +146,15 @@ def main(argv) -> None: output = sys.stdout.buffer try: - if format == "fdx": + if out_format == "fdx": from screenplain.export.fdx import to_fdx - to_fdx(screenplay, output) - elif format == "html": + to_fdx(screenplay, output) # ty: ignore[invalid-argument-type] + elif out_format == "html": from screenplain.export.html import convert - convert(screenplay, output, css_file=args.css, bare=args.bare) - elif format == "pdf": + convert(screenplay, output, css_file=args.css, bare=args.bare) # ty: ignore[invalid-argument-type] + elif out_format == "pdf": from screenplain.export import pdf font_settings = None @@ -163,12 +163,12 @@ def main(argv) -> None: settings = pdf.Settings( font_settings=font_settings, strong_slugs=args.strong ) - pdf.to_pdf(screenplay, output, settings=settings) + pdf.to_pdf(screenplay, output, settings=settings) # ty: ignore[invalid-argument-type] finally: if output_file: output.close() if input_file: - input.close() + input_stream.close() def cli() -> None: diff --git a/screenplain/parsers/fountain.py b/screenplain/parsers/fountain.py index 545ed80..f2bf620 100644 --- a/screenplain/parsers/fountain.py +++ b/screenplain/parsers/fountain.py @@ -4,10 +4,14 @@ import itertools import re +from collections.abc import Generator, Iterable, Iterator +from io import StringIO from itertools import takewhile +from typing import Any -from screenplain.richstring import parse_emphasis, plain +from screenplain.richstring import RichString, parse_emphasis, plain from screenplain.types import ( + SCREENPLAY_TYPES, Action, Dialog, DualDialog, @@ -43,21 +47,21 @@ note_re = re.compile(r"\[\[.*?\]\]", re.DOTALL) -def _sequence_to_rich(lines): +def _sequence_to_rich(lines: Iterable[str]) -> list[RichString]: """Converts a sequence of strings into a list of RichString.""" return [parse_emphasis(line) for line in lines] -def _string_to_rich(line): +def _string_to_rich(line: str) -> RichString: """Converts a single string into a RichString.""" return parse_emphasis(line) class InputParagraph: - def __init__(self, lines): + def __init__(self, lines: list[str]) -> None: self.lines = lines - def update_list(self, previous_paragraphs) -> None: + def update_list(self, previous_paragraphs: list[SCREENPLAY_TYPES]) -> None: """Inserts this paragraph into a list. Modifies the `previous_paragraphs` list. """ @@ -73,7 +77,7 @@ def update_list(self, previous_paragraphs) -> None: or self.append_action(previous_paragraphs) ) - def append_slug(self, paragraphs) -> bool: + def append_slug(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: if len(self.lines) != 1: return False @@ -94,7 +98,9 @@ def append_slug(self, paragraphs) -> bool: paragraphs.append(Slug(_string_to_rich(text))) return True - def append_sections_and_synopsises(self, paragraphs) -> bool: + def append_sections_and_synopsises( + self, paragraphs: list[SCREENPLAY_TYPES] + ) -> bool: new_paragraphs = [] for line in self.lines: @@ -115,26 +121,25 @@ def append_sections_and_synopsises(self, paragraphs) -> bool: paragraphs += new_paragraphs return True - def append_centered_action(self, paragraphs) -> bool: - if not all(centered_re.match(line) for line in self.lines): + def append_centered_action(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: + matches = [] + for line in self.lines: + match = centered_re.match(line) + if not match: + return False + matches.append(match.group(1)) + if not matches: return False - paragraphs.append( - Action( - _sequence_to_rich( - centered_re.match(line).group(1) for line in self.lines - ), - centered=True, - ) - ) + paragraphs.append(Action(_sequence_to_rich(matches), centered=True)) return True - def _create_dialog(self, character): + def _create_dialog(self, character: str) -> Dialog: return Dialog( parse_emphasis(character.strip()), _sequence_to_rich(line.strip() for line in self.lines[1:]), ) - def append_dialog(self, paragraphs) -> bool: + def append_dialog(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: if len(self.lines) < 2: return False @@ -152,6 +157,7 @@ def append_dialog(self, paragraphs) -> bool: dual_match = dual_dialog_re.match(character) if dual_match: previous = paragraphs.pop() + assert isinstance(previous, Dialog) dialog = self._create_dialog(dual_match.group(1)) paragraphs.append(DualDialog(previous, dialog)) return True @@ -159,7 +165,7 @@ def append_dialog(self, paragraphs) -> bool: paragraphs.append(self._create_dialog(character)) return True - def append_transition(self, paragraphs) -> bool: + def append_transition(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: if len(self.lines) != 1: return False @@ -180,13 +186,13 @@ def append_transition(self, paragraphs) -> bool: return False - def append_forced_action(self, paragraphs): + def append_forced_action(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: if self.lines[0].startswith("!"): return self.append_action(paragraphs) else: return False - def append_action(self, paragraphs) -> bool: + def append_action(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: paragraphs.append( Action( _sequence_to_rich( @@ -197,19 +203,19 @@ def append_action(self, paragraphs) -> bool: ) return True - def append_synopsis(self, paragraphs) -> bool: + def append_synopsis(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: if ( len(self.lines) == 1 and self.lines[0].startswith("=") and paragraphs - and hasattr(paragraphs[-1], "set_synopsis") + and isinstance(paragraphs[-1], (Slug, Section)) ): paragraphs[-1].set_synopsis(self.lines[0][1:].lstrip()) return True else: return False - def append_page_break(self, paragraphs) -> bool: + def append_page_break(self, paragraphs: list[SCREENPLAY_TYPES]) -> bool: if len(self.lines) == 1 and page_break_re.match(self.lines[0]): paragraphs.append(PageBreak()) return True @@ -217,7 +223,7 @@ def append_page_break(self, paragraphs) -> bool: return False -def _preprocess_line(raw_line): +def _preprocess_line(raw_line: str) -> str: r"""Replaces tabs with spaces and removes trailing end of line markers. >>> _preprocess_line('foo \r\n\n') @@ -227,11 +233,11 @@ def _preprocess_line(raw_line): return raw_line.expandtabs(4).rstrip("\r\n") -def _is_blank(line): +def _is_blank(line: str) -> bool: return line == "" or line == " " -def parse(stream): +def parse(stream: StringIO) -> Screenplay: """Parses Fountain source. Returns a Screenplay object. @@ -244,13 +250,13 @@ def parse(stream): return parse_lines(lines) -def parse_lines(source): +def parse_lines(source: list[str]) -> Screenplay: """Reads raw text input and generates paragraph objects. Returns a Screenplay object. """ - source = (_preprocess_line(line) for line in source) + source: Generator[str, Any, None] = (_preprocess_line(line) for line in source) title_page_lines = list(takewhile(lambda line: line != "", source)) @@ -272,10 +278,10 @@ def parse_lines(source): ) -def parse_body(source): +def parse_body(source: Iterator[str]) -> list[SCREENPLAY_TYPES]: """Reads lines of the main screenplay and generates paragraph objects.""" - paragraphs = [] + paragraphs: list[SCREENPLAY_TYPES] = [] for blank, input_lines in itertools.groupby(source, _is_blank): if not blank: as_string = note_re.sub("", "\n".join(input_lines)) @@ -287,7 +293,7 @@ def parse_body(source): return paragraphs -def parse_title_page(lines) -> dict[str, list[str]] | None: +def parse_title_page(lines: list[str]) -> dict[str, list[str]] | None: """Parse the title page. Spec: http://fountain.io/syntax#section-titlepage @@ -299,7 +305,7 @@ def parse_title_page(lines) -> dict[str, list[str]] | None: So writing the key as "Author:" and "author:" both will add to the same "Author" key in the resulting dictionary. """ - result = {} + result: dict[str, list[str]] = {} it = iter(lines) try: diff --git a/screenplain/richstring.py b/screenplain/richstring.py index 46bef79..ee8537d 100644 --- a/screenplain/richstring.py +++ b/screenplain/richstring.py @@ -2,13 +2,17 @@ # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php +from __future__ import annotations + import re +from collections.abc import Iterable from html import escape as html_escape +from typing import override _magic_re = re.compile("[\ue700-\ue705]") -def _escape(s): +def _escape(s: str) -> str: """Replaces special HTML characters like < and non-ascii characters with ampersand escapes. @@ -21,18 +25,20 @@ def _escape(s): class RichString: """A sequence of segments where each segment can have its own style.""" - def __init__(self, *segments): + def __init__(self, *segments: Segment) -> None: self.segments = segments - def __repr__(self): + @override + def __repr__(self) -> str: if not self.segments: return "empty_string" return " + ".join(repr(s) for s in self.segments) - def __str__(self): + @override + def __str__(self) -> str: return "".join(str(s) for s in self.segments) - def startswith(self, string): + def startswith(self, string: str) -> bool: """Checks if the first segment in this string starts with a specific string. @@ -43,7 +49,7 @@ def startswith(self, string): return False return self.segments[0].text.startswith(string) - def endswith(self, string): + def endswith(self, string: str) -> bool: """Checks if the last segment in this string ends with a specific string. @@ -54,21 +60,23 @@ def endswith(self, string): return False return self.segments[-1].text.endswith(string) - def to_html(self): + def to_html(self) -> str: html = "".join(seg.to_html() for seg in self.segments) if html.startswith(" "): return " " + html[1:] else: return html - def __eq__(self, other): + @override + def __eq__(self, other: object) -> bool: return isinstance(other, RichString) and self.segments == other.segments - def __ne__(self, other): + @override + def __ne__(self, other: object) -> bool: return isinstance(other, RichString) and self.segments != other.segments - def __add__(self, other): - if hasattr(other, "segments"): + def __add__(self, other: object) -> RichString: + if isinstance(other, RichString): return RichString(*(self.segments + other.segments)) else: raise ValueError("Concatenating requires RichString") @@ -77,43 +85,47 @@ def __add__(self, other): class Segment: """A piece of a rich string. Has a set of styles.""" - def __init__(self, text, styles): + def __init__(self, text: str, styles: Iterable[type[Style]]) -> None: """ Creates a segment with a set of styles. text is the raw text string, and styles is a set of Style subclasses. """ - self.styles = set(styles) - self.text = text + self.styles: set[type[Style]] = set(styles) + self.text: str = text - def __repr__(self): + @override + def __repr__(self) -> str: styles = ( "+".join(style.name() for style in self.get_ordered_styles()) or "plain" ) return f"({styles})({self.text!r})" - def __str__(self): + @override + def __str__(self) -> str: return self.text - def __eq__(self, other): + @override + def __eq__(self, other: object) -> bool: return ( isinstance(other, Segment) and self.text == other.text and self.styles == other.styles ) - def __ne__(self, other): + @override + def __ne__(self, other: object) -> bool: return ( not isinstance(other, Segment) or self.text != other.text or self.styles != other.styles ) - def get_ordered_styles(self): + def get_ordered_styles(self) -> list[type[Style]]: """Get the styles in this segment in a deterministic order.""" return [style for style in all_styles if style in self.styles] - def to_html(self): + def to_html(self) -> str: ordered_styles = self.get_ordered_styles() return ( "".join(style.start_html for style in ordered_styles) @@ -129,13 +141,15 @@ def to_html(self): class Style: """Abstract base class for styles""" + parse_re: re.Pattern[str] + start_magic = "" end_magic = "" start_html = "" end_html = "" @classmethod - def name(cls): + def name(cls) -> str: return cls.__name__.lower() @@ -201,13 +215,15 @@ class _CreateStyledString: with a single segment with a specified style. """ - def __init__(self, styles): + def __init__(self, styles: Iterable[type[Style]]) -> None: self.styles = set(styles) - def __call__(self, text): + def __call__(self, text: str) -> RichString: return RichString(Segment(text, self.styles)) - def __add__(self, other): + def __add__(self, other: object) -> _CreateStyledString: + if not isinstance(other, _CreateStyledString): + return NotImplemented return _CreateStyledString(self.styles.union(other.styles)) @@ -222,10 +238,10 @@ def __add__(self, other): literal_star = "\ue706" # All styles. Note: order matters! This is the order they are parsed. -all_styles = (Bold, Italic, Underline) +all_styles: tuple[type[Style], ...] = (Bold, Italic, Underline) -def _unescape(source): +def _unescape(source: str) -> str: r"""Converts backslash-escaped stars in a string to the magic "literal star" character. @@ -236,7 +252,7 @@ def _unescape(source): return source.replace("\\*", literal_star) -def _demagic_literals(text): +def _demagic_literals(text: str) -> str: r"""Converts "literal star" characters to actual stars: "*" >>> _demagic_literals('\ue706hello\ue706') @@ -245,7 +261,7 @@ def _demagic_literals(text): return text.replace(literal_star, "*") -def parse_emphasis(source): +def parse_emphasis(source: str) -> RichString: """Parses emphasis markers like * and ** in a string and returns a RichString object. @@ -271,7 +287,7 @@ def parse_emphasis(source): segments = [] pos = 0 - def append(pos, end): + def append(pos: int, end: int) -> None: if pos == end: return text = source[pos:end] diff --git a/screenplain/types.py b/screenplain/types.py index fffa6cb..652b6d6 100644 --- a/screenplain/types.py +++ b/screenplain/types.py @@ -2,11 +2,24 @@ # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php -from screenplain.richstring import parse_emphasis +from __future__ import annotations + +from collections.abc import Iterator +from typing import override + +from screenplain.richstring import RichString, parse_emphasis + +type SCREENPLAY_TYPES = ( + Slug | Section | Dialog | DualDialog | Action | Transition | PageBreak +) class Screenplay: - def __init__(self, title_page=None, paragraphs=None): + def __init__( + self, + title_page: dict[str, list[str]] | None = None, + paragraphs: list[SCREENPLAY_TYPES] | None = None, + ) -> None: """ Create a Screenplay object. @@ -16,17 +29,19 @@ def __init__(self, title_page=None, paragraphs=None): # Key/value pairs for title page if title_page is None: - self.title_page = {} + self.title_page: dict[str, list[str]] = {} else: - self.title_page = title_page + self.title_page: dict[str, list[str]] = title_page # The paragraphs of the actual script if paragraphs is None: - self.paragraphs = [] + self.paragraphs: list[SCREENPLAY_TYPES] = [] else: - self.paragraphs = paragraphs + self.paragraphs: list[SCREENPLAY_TYPES] = paragraphs - def get_rich_attribute(self, name, default=None): + def get_rich_attribute( + self, name: str, default: list[RichString] | None = None + ) -> list[RichString]: """Get an attribute from the title page parsed into a RichString. Returns a list of RichString objects. @@ -40,49 +55,58 @@ def get_rich_attribute(self, name, default=None): else: return default - def append(self, paragraph) -> None: + def append(self, paragraph: SCREENPLAY_TYPES) -> None: """Append a paragraph to this screenplay.""" self.paragraphs.append(paragraph) - def __iter__(self): + def __iter__(self) -> Iterator[SCREENPLAY_TYPES]: """Get an iterator over the paragraphs of this screenplay.""" return iter(self.paragraphs) class Slug: - def __init__(self, line, scene_number=None): + def __init__( + self, line: RichString, scene_number: RichString | None = None + ) -> None: """Creates a scene heading (slug). The line parameter is a RichString with the slugline. The scene_number parameter is an optional RichString. """ - self.line = line + self.line: RichString = line self.scene_number = scene_number self.synopsis = None @property - def lines(self): + def lines(self) -> list[RichString]: return [self.line] - def set_synopsis(self, text) -> None: + def set_synopsis(self, text: str) -> None: self.synopsis = text class Section: """A section heading.""" - def __init__(self, text, level, synopsis=None): - self.text = text - self.level = level - self.synopsis = synopsis + def __init__( + self, text: RichString, level: int, synopsis: str | None = None + ) -> None: + self.text: RichString = text + self.level: int = level + self.synopsis: str | None = synopsis - def set_synopsis(self, text) -> None: + def set_synopsis(self, text: str) -> None: self.synopsis = text - def __repr__(self): + @override + def __repr__(self) -> str: return f"Section({self.text!r}, {self.level!r}, {self.synopsis!r})" - def __eq__(self, other): + @override + def __eq__(self, other: object) -> bool: + if not isinstance(other, Section): + return NotImplemented + return ( self.text == other.text and self.level == other.level @@ -91,13 +115,15 @@ def __eq__(self, other): class Dialog: - def __init__(self, character, lines=None): - self.character = character + def __init__( + self, character: RichString, lines: list[RichString] | None = None + ) -> None: + self.character: RichString = character self.blocks = [] # list of tuples of (is_parenthetical, text) if lines: self._parse(lines) - def _parse(self, lines): + def _parse(self, lines: list[RichString]) -> None: inside_parenthesis = False for line in lines: if line.startswith("("): @@ -106,29 +132,29 @@ def _parse(self, lines): if line.endswith(")"): inside_parenthesis = False - def add_line(self, line) -> None: + def add_line(self, line: str) -> None: parenthetical = line.startswith("(") self.blocks.append((parenthetical, line)) class DualDialog: - def __init__(self, left_dialog, right_dialog): - self.left = left_dialog - self.right = right_dialog + def __init__(self, left_dialog: Dialog, right_dialog: Dialog) -> None: + self.left: Dialog = left_dialog + self.right: Dialog = right_dialog class Action: - def __init__(self, lines, centered=False): - self.lines = lines + def __init__(self, lines: list[RichString], centered: bool = False) -> None: + self.lines: list[RichString] = lines self.centered = centered class Transition: - def __init__(self, line): + def __init__(self, line: RichString) -> None: self.line = line @property - def lines(self): + def lines(self) -> list[RichString]: return [self.line] diff --git a/tests/fdx_test.py b/tests/fdx_test.py index 5d3a6fc..7934dd0 100644 --- a/tests/fdx_test.py +++ b/tests/fdx_test.py @@ -3,6 +3,7 @@ # http://www.opensource.org/licenses/mit-license.php from io import StringIO +from typing import override from unittest import TestCase from screenplain.export.fdx import write_text @@ -10,6 +11,7 @@ class OutputTests(TestCase): + @override def setUp(self) -> None: self.out = StringIO() diff --git a/tests/files/dialogue-with-blank-line.fountain b/tests/files/dialogue-with-blank-line.fountain index 97da070..a64a9f7 100644 --- a/tests/files/dialogue-with-blank-line.fountain +++ b/tests/files/dialogue-with-blank-line.fountain @@ -1,4 +1,4 @@ DEALER Dealer gets a seven. - + Hit or stand sir? diff --git a/tests/files/notes.fountain b/tests/files/notes.fountain index 317752f..f72b4fc 100644 --- a/tests/files/notes.fountain +++ b/tests/files/notes.fountain @@ -6,7 +6,7 @@ here]]After2 Before3 [[multiline note - + with a blank line]]After3 Multiple [[notes]]on one[[line]] diff --git a/tests/files_test.py b/tests/files_test.py index 6e8a0fd..32abe25 100644 --- a/tests/files_test.py +++ b/tests/files_test.py @@ -2,11 +2,14 @@ # Licensed under the MIT license: # http://www.opensource.org/licenses/mit-license.php +from __future__ import annotations + import os import os.path import re import shutil import tempfile +from typing import override from unittest import TestCase from screenplain.main import main @@ -17,12 +20,12 @@ spaces_re = re.compile("[ \t]+") -def read_file(path): +def read_file(path: str) -> str: with open(path, encoding="utf-8") as stream: return stream.read() -def clean_string(s): +def clean_string(s: str) -> str: r"""Removes duplicated spaces and line breaks in a string. >>> clean_string('foo \n \nbar\n\n') @@ -39,19 +42,27 @@ class FileTests(TestCase): maxDiff = None + @override def setUp(self) -> None: self.dir = tempfile.mkdtemp() + @override def tearDown(self) -> None: shutil.rmtree(self.dir) - def source(self, filename): + def source(self, filename: str) -> str: return os.path.join(source_dir, filename) - def target(self, name): + def target(self, name: str) -> str: return os.path.join(self.dir, name) - def convert(self, input_file, output_file, expected_results_file, *options): + def convert( + self, + input_file: str, + output_file: str, + expected_results_file: str, + *options: str, + ) -> tuple[str, str]: input_path = self.source(input_file) output_path = self.target(output_file) main([*list(options), input_path, output_path]) @@ -60,7 +71,7 @@ def convert(self, input_file, output_file, expected_results_file, *options): return clean_string(actual), clean_string(expected) @classmethod - def add_file_case(cls, source_file, expected_results_file): + def add_file_case(cls, source_file: str, expected_results_file: str) -> None: """Add a test case that compares the content of a generated file with the expected results. @@ -68,7 +79,7 @@ def add_file_case(cls, source_file, expected_results_file): base, _ = os.path.splitext(source_file) extension = os.path.splitext(expected_results_file)[1][1:] - def test_function(self): + def test_function(self: FileTests) -> None: actual, expected = self.convert( source_file, base + "." + extension, expected_results_file, "--bare" ) @@ -78,7 +89,7 @@ def test_function(self): setattr(cls, func_name, test_function) -def _create_tests(): +def _create_tests() -> None: """Creates a test case for every source/expect file pair. Finds all the source files in the test files directory. diff --git a/tests/fountain_test.py b/tests/fountain_test.py index 8229ceb..b3d7b4e 100644 --- a/tests/fountain_test.py +++ b/tests/fountain_test.py @@ -8,6 +8,7 @@ from screenplain.parsers import fountain from screenplain.richstring import empty_string, italic, plain from screenplain.types import ( + SCREENPLAY_TYPES, Action, Dialog, DualDialog, @@ -18,7 +19,7 @@ ) -def parse(lines): +def parse(lines: list[str]) -> list[SCREENPLAY_TYPES]: content = "\n".join(lines) return list(fountain.parse(StringIO(content))) @@ -32,7 +33,9 @@ def test_slug_with_prefix(self) -> None: "THIS IS JUST ACTION", ] ) - self.assertEqual([Slug, Action], [type(p) for p in paras]) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Slug) + assert isinstance(paras[1], Action) def test_slug_must_be_single_line(self) -> None: paras = parse( @@ -43,7 +46,9 @@ def test_slug_must_be_single_line(self) -> None: "Some action", ] ) - self.assertEqual([Dialog, Action], [type(p) for p in paras]) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Dialog) + assert isinstance(paras[1], Action) # What looks like a scene headingis parsed as a character name. # Unexpected perhaps, but that's how I interpreted the spec. self.assertEqual(plain("INT. SOMEWHERE - DAY"), paras[0].character) @@ -56,22 +61,21 @@ def test_action_is_not_a_slug(self) -> None: "THIS IS JUST ACTION", ] ) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) def test_two_lines_creates_no_slug(self) -> None: - types = [ - type(p) - for p in parse( - [ - "", - "", - "This is a slug", - "", - ] - ) - ] + types = parse( + [ + "", + "", + "This is a slug", + "", + ] + ) # This used to be Slug. Changed in the Jan 2012 version of the spec. - self.assertEqual([Action], types) + self.assertEqual(1, len(types)) + assert isinstance(types[0], Action) def test_period_creates_slug(self) -> None: paras = parse( @@ -81,7 +85,7 @@ def test_period_creates_slug(self) -> None: ] ) self.assertEqual(1, len(paras)) - self.assertEqual(Slug, type(paras[0])) + assert isinstance(paras[0], Slug) self.assertEqual(plain("SNIPER SCOPE POV"), paras[0].line) def test_more_than_one_period_does_not_create_slug(self) -> None: @@ -92,24 +96,30 @@ def test_more_than_one_period_does_not_create_slug(self) -> None: ] ) self.assertEqual(1, len(paras)) - self.assertEqual(Action, type(paras[0])) + assert isinstance(paras[0], Action) self.assertEqual(plain("..AND THEN..."), paras[0].lines[0]) def test_scene_number_is_parsed(self) -> None: paras = parse(["EXT SOMEWHERE - DAY #42#"]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Slug) self.assertEqual(plain("EXT SOMEWHERE - DAY"), paras[0].line) self.assertEqual(plain("42"), paras[0].scene_number) def test_only_last_two_hashes_in_slug_used_for_scene_number(self) -> None: paras = parse(["INT ROOM #237 #42#"]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Slug) self.assertEqual(plain("42"), paras[0].scene_number) self.assertEqual(plain("INT ROOM #237"), paras[0].line) def test_scene_number_must_be_alphanumeric(self) -> None: paras = parse([".SOMEWHERE #*HELLO*#"]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Slug) self.assertIsNone(paras[0].scene_number) self.assertEqual( - (plain)("SOMEWHERE #") + (italic)("HELLO") + (plain)("#"), paras[0].line + plain("SOMEWHERE #") + italic("HELLO") + plain("#"), paras[0].line ) @@ -122,19 +132,27 @@ def test_section_parsed_correctly(self) -> None: "## second level", ] ) - self.assertEqual([Section, Section], [type(p) for p in paras]) + assert isinstance(paras[0], Section) self.assertEqual(1, paras[0].level) self.assertEqual(plain("first level"), paras[0].text) + + assert isinstance(paras[1], Section) self.assertEqual(2, paras[1].level) self.assertEqual(plain("second level"), paras[1].text) def test_multiple_sections_in_one_paragraph(self) -> None: paras = parse(["# first level", "## second level", "# first level again"]) - self.assertEqual([Section, Section, Section], [type(p) for p in paras]) + self.assertEqual(3, len(paras)) + + assert isinstance(paras[0], Section) self.assertEqual(1, paras[0].level) self.assertEqual(plain("first level"), paras[0].text) + + assert isinstance(paras[1], Section) self.assertEqual(2, paras[1].level) self.assertEqual(plain("second level"), paras[1].text) + + assert isinstance(paras[2], Section) self.assertEqual(1, paras[2].level) self.assertEqual(plain("first level again"), paras[2].text) @@ -169,6 +187,7 @@ def test_all_caps_is_character(self) -> None: ) ] self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Dialog) dialog = paras[0] self.assertEqual(Dialog, type(dialog)) self.assertEqual(plain("SOME GUY"), dialog.character) @@ -180,7 +199,8 @@ def test_alphanumeric_character(self) -> None: "Bee-bop", ] ) - self.assertEqual([Dialog], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Dialog) self.assertEqual(plain("R2D2"), paras[0].character) # Spec http://fountain.io/syntax#section-character: @@ -193,7 +213,8 @@ def test_nonalpha_character(self) -> None: "Hello", ] ) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) # Spec http://fountain.io/syntax#section-character: # You can force a Character element by preceding it with the "at" symbol @. @@ -204,7 +225,8 @@ def test_at_sign_forces_dialog(self) -> None: "Yippee ki-yay", ] ) - self.assertEqual([Dialog], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Dialog) self.assertEqual(plain("McCLANE"), paras[0].character) def test_twospaced_line_is_not_character(self) -> None: @@ -214,7 +236,8 @@ def test_twospaced_line_is_not_character(self) -> None: "Where is that pit boss?", ] ) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) def test_simple_parenthetical(self) -> None: paras = parse( @@ -225,6 +248,7 @@ def test_simple_parenthetical(self) -> None: ] ) self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Dialog) dialog = paras[0] self.assertEqual(2, len(dialog.blocks)) self.assertEqual((True, plain("(starting the engine)")), dialog.blocks[0]) @@ -239,7 +263,8 @@ def test_twospace_keeps_dialog_together(self) -> None: "Two", ] ) - self.assertEqual([Dialog], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Dialog) self.assertEqual( [ (False, plain("One")), @@ -259,7 +284,8 @@ def test_dual_dialog(self) -> None: "Fuck retirement!", ] ) - self.assertEqual([DualDialog], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], DualDialog) dual = paras[0] self.assertEqual(plain("BRICK"), dual.left.character) self.assertEqual([(False, plain("Fuck retirement."))], dual.left.blocks) @@ -275,6 +301,9 @@ def test_dual_dialog_without_previous_dialog_is_ignored(self) -> None: "Nice retirement.", ] ) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Dialog) self.assertEqual([Action, Dialog], [type(p) for p in paras]) dialog = paras[1] self.assertEqual(plain("BRICK ^"), dialog.character) @@ -290,7 +319,8 @@ def test_leading_and_trailing_spaces_in_dialog(self) -> None: " And I'll no longer be a Capulet.", ] ) - self.assertEqual([Dialog], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Dialog) self.assertEqual( [ (False, plain("O Romeo, Romeo! wherefore art thou Romeo?")), @@ -313,7 +343,10 @@ def test_standard_transition(self) -> None: "EXT. BRICK'S POOL - DAY", ] ) - self.assertEqual([Action, Transition, Slug], [type(p) for p in paras]) + self.assertEqual(3, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Transition) + assert isinstance(paras[2], Slug) def test_transition_must_end_with_to(self) -> None: paras = parse( @@ -323,7 +356,9 @@ def test_transition_must_end_with_to(self) -> None: "EXT. BRICK'S POOL - DAY", ] ) - self.assertEqual([Action, Slug], [type(p) for p in paras]) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Slug) def test_transition_needs_to_be_upper_case(self) -> None: paras = parse( @@ -335,7 +370,10 @@ def test_transition_needs_to_be_upper_case(self) -> None: "EXT. BRICK'S POOL - DAY", ] ) - self.assertEqual([Action, Action, Slug], [type(p) for p in paras]) + self.assertEqual(3, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Action) + assert isinstance(paras[2], Slug) def test_not_a_transition_on_trailing_whitespace(self) -> None: paras = parse( @@ -347,7 +385,10 @@ def test_not_a_transition_on_trailing_whitespace(self) -> None: "EXT. BRICK'S POOL - DAY", ] ) - self.assertEqual([Action, Action, Slug], [type(p) for p in paras]) + self.assertEqual(3, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Action) + assert isinstance(paras[2], Slug) def test_transition_does_not_have_to_be_followed_by_slug(self) -> None: # The "followed by slug" requirement is gone from the Jan 2012 spec @@ -360,7 +401,10 @@ def test_transition_does_not_have_to_be_followed_by_slug(self) -> None: "SOME GUY mowing the lawn.", ] ) - self.assertEqual([Action, Transition, Action], [type(p) for p in paras]) + self.assertEqual(3, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Transition) + assert isinstance(paras[2], Action) def test_greater_than_sign_means_transition(self) -> None: paras = parse( @@ -372,7 +416,10 @@ def test_greater_than_sign_means_transition(self) -> None: ".DARKNESS", ] ) - self.assertEqual([Action, Transition, Slug], [type(p) for p in paras]) + self.assertEqual(3, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Transition) + assert isinstance(paras[2], Slug) self.assertEqual(plain("FADE OUT."), paras[1].line) def test_centered_text_is_not_parsed_as_transition(self) -> None: @@ -387,7 +434,9 @@ def test_transition_at_end(self) -> None: "> FADE OUT.", ] ) - self.assertEqual([Action, Transition], [type(p) for p in paras]) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Transition) self.assertEqual(plain("FADE OUT."), paras[1].line) @@ -401,7 +450,9 @@ def test_action_preserves_leading_whitespace(self) -> None: " three spaces ", ] ) - self.assertEqual([Action, Action], [type(p) for p in paras]) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Action) self.assertEqual( [ plain(" two spaces"), @@ -412,7 +463,8 @@ def test_action_preserves_leading_whitespace(self) -> None: def test_single_centered_line(self) -> None: paras = parse(["> center me! <"]) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) self.assertTrue(paras[0].centered) def test_full_centered_paragraph(self) -> None: @@ -422,7 +474,8 @@ def test_full_centered_paragraph(self) -> None: "> third!< ", ] paras = parse(lines) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) self.assertTrue(paras[0].centered) self.assertEqual( [ @@ -441,7 +494,8 @@ def test_upper_case_centered_not_parsed_as_dialog(self) -> None: "> THIRD! <", ] ) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) self.assertTrue(paras[0].centered) def test_centering_marks_in_middle_of_paragraphs_are_verbatim(self) -> None: @@ -451,7 +505,8 @@ def test_centering_marks_in_middle_of_paragraphs_are_verbatim(self) -> None: "third!", ] paras = parse(lines) - self.assertEqual([Action], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Action) self.assertFalse(paras[0].centered) self.assertEqual([plain(line) for line in lines], paras[0].lines) @@ -465,19 +520,24 @@ def test_synopsis_after_slug_adds_synopsis_to_scene(self) -> None: "= Set up Brick & Steel's new life.", ] ) - self.assertEqual([Slug], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Slug) self.assertEqual("Set up Brick & Steel's new life.", paras[0].synopsis) def test_synopsis_in_section(self) -> None: paras = parse(["# section one", "", "= In which we get to know our characters"]) - self.assertEqual([Section], [type(p) for p in paras]) + self.assertEqual(1, len(paras)) + assert isinstance(paras[0], Section) self.assertEqual("In which we get to know our characters", paras[0].synopsis) def test_synopsis_syntax_parsed_as_literal(self) -> None: paras = parse( ["Some action", "", "= A line that just happens to look like a synopsis"] ) - self.assertEqual([Action, Action], [type(p) for p in paras]) + self.assertEqual(2, len(paras)) + assert isinstance(paras[0], Action) + assert isinstance(paras[1], Action) + self.assertEqual( [plain("= A line that just happens to look like a synopsis")], paras[1].lines, @@ -492,12 +552,14 @@ def test_basic_title_page(self) -> None: " _**FULL RETIRED**_", "Author: Stu Maschwitz", ] + result = fountain.parse_title_page(lines) + assert result is not None self.assertDictEqual( { "Title": ["_**BRICK & STEEL**_", "_**FULL RETIRED**_"], "Author": ["Stu Maschwitz"], }, - fountain.parse_title_page(lines), + result, ) def test_multiple_values(self) -> None: @@ -507,9 +569,11 @@ def test_multiple_values(self) -> None: "Title:", " (which happens to be true)", ] + result = fountain.parse_title_page(lines) + assert result is not None self.assertDictEqual( {"Title": ["Death", "- a love story", "(which happens to be true)"]}, - fountain.parse_title_page(lines), + result, ) def test_key_casing(self) -> None: @@ -518,12 +582,14 @@ def test_key_casing(self) -> None: "Author: bruce", "draft DATE: 1/10/2026", ] + result = fountain.parse_title_page(lines) + assert result is not None self.assertDictEqual( { "Author": ["bruce"], "Draft date": ["1/10/2026"], }, - fountain.parse_title_page(lines), + result, ) def test_multiple_values_with_different_case(self) -> None: @@ -531,6 +597,8 @@ def test_multiple_values_with_different_case(self) -> None: "Title: Death", "title: - a love story", ] + result = fountain.parse_title_page(lines) + assert result is not None self.assertDictEqual( { "Title": [ @@ -538,7 +606,7 @@ def test_multiple_values_with_different_case(self) -> None: "- a love story", ] }, - fountain.parse_title_page(lines), + result, ) def test_empty_value_ignored(self) -> None: @@ -546,9 +614,9 @@ def test_empty_value_ignored(self) -> None: "Title:", "Author: John August", ] - self.assertDictEqual( - {"Author": ["John August"]}, fountain.parse_title_page(lines) - ) + result = fountain.parse_title_page(lines) + assert result is not None + self.assertDictEqual({"Author": ["John August"]}, result) def test_unparsable_title_page_returns_none(self) -> None: lines = [ diff --git a/tests/visual/pdf_test.py b/tests/visual/pdf_test.py index b25efc1..515bae7 100755 --- a/tests/visual/pdf_test.py +++ b/tests/visual/pdf_test.py @@ -18,7 +18,7 @@ import screenplain.main -def compare(directory) -> bool: +def compare(directory: str) -> bool: """Test that PDF generation produces consistent output.""" reference_dir = Path(directory) fountain_files = list(reference_dir.glob("*.fountain")) diff --git a/uv.lock b/uv.lock index 5adda19..541a106 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.9" -resolution-markers = [ - "python_full_version >= '3.10'", - "python_full_version < '3.10'", -] +requires-python = ">=3.12" [[package]] name = "charset-normalizer" @@ -12,38 +8,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, @@ -92,22 +56,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, - { url = "https://files.pythonhosted.org/packages/46/7c/0c4760bccf082737ca7ab84a4c2034fcc06b1f21cf3032ea98bd6feb1725/charset_normalizer-3.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", size = 209609, upload-time = "2025-10-14T04:42:10.922Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a4/69719daef2f3d7f1819de60c9a6be981b8eeead7542d5ec4440f3c80e111/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", size = 149029, upload-time = "2025-10-14T04:42:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/e6/21/8d4e1d6c1e6070d3672908b8e4533a71b5b53e71d16828cc24d0efec564c/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608", size = 144580, upload-time = "2025-10-14T04:42:13.549Z" }, - { url = "https://files.pythonhosted.org/packages/a7/0a/a616d001b3f25647a9068e0b9199f697ce507ec898cacb06a0d5a1617c99/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", size = 162340, upload-time = "2025-10-14T04:42:14.892Z" }, - { url = "https://files.pythonhosted.org/packages/85/93/060b52deb249a5450460e0585c88a904a83aec474ab8e7aba787f45e79f2/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", size = 159619, upload-time = "2025-10-14T04:42:16.676Z" }, - { url = "https://files.pythonhosted.org/packages/dd/21/0274deb1cc0632cd587a9a0ec6b4674d9108e461cb4cd40d457adaeb0564/charset_normalizer-3.4.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", size = 153980, upload-time = "2025-10-14T04:42:17.917Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/e3d7d982858dccc11b31906976323d790dded2017a0572f093ff982d692f/charset_normalizer-3.4.4-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", size = 152174, upload-time = "2025-10-14T04:42:19.018Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ff/4a269f8e35f1e58b2df52c131a1fa019acb7ef3f8697b7d464b07e9b492d/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", size = 151666, upload-time = "2025-10-14T04:42:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/da/c9/ec39870f0b330d58486001dd8e532c6b9a905f5765f58a6f8204926b4a93/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", size = 145550, upload-time = "2025-10-14T04:42:21.324Z" }, - { url = "https://files.pythonhosted.org/packages/75/8f/d186ab99e40e0ed9f82f033d6e49001701c81244d01905dd4a6924191a30/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", size = 163721, upload-time = "2025-10-14T04:42:22.46Z" }, - { url = "https://files.pythonhosted.org/packages/96/b1/6047663b9744df26a7e479ac1e77af7134b1fcf9026243bb48ee2d18810f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", size = 152127, upload-time = "2025-10-14T04:42:23.712Z" }, - { url = "https://files.pythonhosted.org/packages/59/78/e5a6eac9179f24f704d1be67d08704c3c6ab9f00963963524be27c18ed87/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", size = 161175, upload-time = "2025-10-14T04:42:24.87Z" }, - { url = "https://files.pythonhosted.org/packages/e5/43/0e626e42d54dd2f8dd6fc5e1c5ff00f05fbca17cb699bedead2cae69c62f/charset_normalizer-3.4.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", size = 155375, upload-time = "2025-10-14T04:42:27.246Z" }, - { url = "https://files.pythonhosted.org/packages/e9/91/d9615bf2e06f35e4997616ff31248c3657ed649c5ab9d35ea12fce54e380/charset_normalizer-3.4.4-cp39-cp39-win32.whl", hash = "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", size = 99692, upload-time = "2025-10-14T04:42:28.425Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a9/6c040053909d9d1ef4fcab45fddec083aedc9052c10078339b47c8573ea8/charset_normalizer-3.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", size = 107192, upload-time = "2025-10-14T04:42:29.482Z" }, - { url = "https://files.pythonhosted.org/packages/f0/c6/4fa536b2c0cd3edfb7ccf8469fa0f363ea67b7213a842b90909ca33dd851/charset_normalizer-3.4.4-cp39-cp39-win_arm64.whl", hash = "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", size = 100220, upload-time = "2025-10-14T04:42:30.632Z" }, { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] @@ -120,37 +68,10 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "exceptiongroup" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "python_full_version < '3.13'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, -] - -[[package]] -name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, -] - [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, @@ -165,153 +86,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/5d/45a3553a253ac8763f3561371432a90bdbe6000fbdcf1397ffe502aa206c/pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860", size = 5316554, upload-time = "2025-07-01T09:13:39.342Z" }, - { url = "https://files.pythonhosted.org/packages/7c/c8/67c12ab069ef586a25a4a79ced553586748fad100c77c0ce59bb4983ac98/pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad", size = 4686548, upload-time = "2025-07-01T09:13:41.835Z" }, - { url = "https://files.pythonhosted.org/packages/2f/bd/6741ebd56263390b382ae4c5de02979af7f8bd9807346d068700dd6d5cf9/pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0", size = 5859742, upload-time = "2025-07-03T13:09:47.439Z" }, - { url = "https://files.pythonhosted.org/packages/ca/0b/c412a9e27e1e6a829e6ab6c2dca52dd563efbedf4c9c6aa453d9a9b77359/pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b", size = 7633087, upload-time = "2025-07-03T13:09:51.796Z" }, - { url = "https://files.pythonhosted.org/packages/59/9d/9b7076aaf30f5dd17e5e5589b2d2f5a5d7e30ff67a171eb686e4eecc2adf/pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50", size = 5963350, upload-time = "2025-07-01T09:13:43.865Z" }, - { url = "https://files.pythonhosted.org/packages/f0/16/1a6bf01fb622fb9cf5c91683823f073f053005c849b1f52ed613afcf8dae/pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae", size = 6631840, upload-time = "2025-07-01T09:13:46.161Z" }, - { url = "https://files.pythonhosted.org/packages/7b/e6/6ff7077077eb47fde78739e7d570bdcd7c10495666b6afcd23ab56b19a43/pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9", size = 6074005, upload-time = "2025-07-01T09:13:47.829Z" }, - { url = "https://files.pythonhosted.org/packages/c3/3a/b13f36832ea6d279a697231658199e0a03cd87ef12048016bdcc84131601/pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e", size = 6708372, upload-time = "2025-07-01T09:13:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e4/61b2e1a7528740efbc70b3d581f33937e38e98ef3d50b05007267a55bcb2/pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6", size = 6277090, upload-time = "2025-07-01T09:13:53.915Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d3/60c781c83a785d6afbd6a326ed4d759d141de43aa7365725cbcd65ce5e54/pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f", size = 6985988, upload-time = "2025-07-01T09:13:55.699Z" }, - { url = "https://files.pythonhosted.org/packages/9f/28/4f4a0203165eefb3763939c6789ba31013a2e90adffb456610f30f613850/pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f", size = 2422899, upload-time = "2025-07-01T09:13:57.497Z" }, - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, - { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, - { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, - { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, - { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, - { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, - { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, - { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, - { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, - { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, - { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, - { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, - { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, - { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, - { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, - { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, - { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, - { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, - { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, - { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, - { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, - { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, - { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, - { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, - { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, - { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, - { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, - { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, - { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, - { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, - { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, - { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, - { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8e/9c089f01677d1264ab8648352dcb7773f37da6ad002542760c80107da816/pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f", size = 5316478, upload-time = "2025-07-01T09:15:52.209Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a9/5749930caf674695867eb56a581e78eb5f524b7583ff10b01b6e5048acb3/pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081", size = 4686522, upload-time = "2025-07-01T09:15:54.162Z" }, - { url = "https://files.pythonhosted.org/packages/43/46/0b85b763eb292b691030795f9f6bb6fcaf8948c39413c81696a01c3577f7/pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4", size = 5853376, upload-time = "2025-07-03T13:11:01.066Z" }, - { url = "https://files.pythonhosted.org/packages/5e/c6/1a230ec0067243cbd60bc2dad5dc3ab46a8a41e21c15f5c9b52b26873069/pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc", size = 7626020, upload-time = "2025-07-03T13:11:06.479Z" }, - { url = "https://files.pythonhosted.org/packages/63/dd/f296c27ffba447bfad76c6a0c44c1ea97a90cb9472b9304c94a732e8dbfb/pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06", size = 5956732, upload-time = "2025-07-01T09:15:56.111Z" }, - { url = "https://files.pythonhosted.org/packages/a5/a0/98a3630f0b57f77bae67716562513d3032ae70414fcaf02750279c389a9e/pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a", size = 6624404, upload-time = "2025-07-01T09:15:58.245Z" }, - { url = "https://files.pythonhosted.org/packages/de/e6/83dfba5646a290edd9a21964da07674409e410579c341fc5b8f7abd81620/pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978", size = 6067760, upload-time = "2025-07-01T09:16:00.003Z" }, - { url = "https://files.pythonhosted.org/packages/bc/41/15ab268fe6ee9a2bc7391e2bbb20a98d3974304ab1a406a992dcb297a370/pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d", size = 6700534, upload-time = "2025-07-01T09:16:02.29Z" }, - { url = "https://files.pythonhosted.org/packages/64/79/6d4f638b288300bed727ff29f2a3cb63db054b33518a95f27724915e3fbc/pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71", size = 6277091, upload-time = "2025-07-01T09:16:04.4Z" }, - { url = "https://files.pythonhosted.org/packages/46/05/4106422f45a05716fd34ed21763f8ec182e8ea00af6e9cb05b93a247361a/pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada", size = 6986091, upload-time = "2025-07-01T09:16:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/63/c6/287fd55c2c12761d0591549d48885187579b7c257bef0c6660755b0b59ae/pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb", size = 2422632, upload-time = "2025-07-01T09:16:08.142Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8b/209bd6b62ce8367f47e68a218bffac88888fdf2c9fcf1ecadc6c3ec1ebc7/pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967", size = 5270556, upload-time = "2025-07-01T09:16:09.961Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/231a0b76070c2cfd9e260a7a5b504fb72da0a95279410fa7afd99d9751d6/pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe", size = 4654625, upload-time = "2025-07-01T09:16:11.913Z" }, - { url = "https://files.pythonhosted.org/packages/13/f4/10cf94fda33cb12765f2397fc285fa6d8eb9c29de7f3185165b702fc7386/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c", size = 4874207, upload-time = "2025-07-03T13:11:10.201Z" }, - { url = "https://files.pythonhosted.org/packages/72/c9/583821097dc691880c92892e8e2d41fe0a5a3d6021f4963371d2f6d57250/pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25", size = 6583939, upload-time = "2025-07-03T13:11:15.68Z" }, - { url = "https://files.pythonhosted.org/packages/3b/8e/5c9d410f9217b12320efc7c413e72693f48468979a013ad17fd690397b9a/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27", size = 4957166, upload-time = "2025-07-01T09:16:13.74Z" }, - { url = "https://files.pythonhosted.org/packages/62/bb/78347dbe13219991877ffb3a91bf09da8317fbfcd4b5f9140aeae020ad71/pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a", size = 5581482, upload-time = "2025-07-01T09:16:16.107Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/1000353d5e61498aaeaaf7f1e4b49ddb05f2c6575f9d4f9f914a3538b6e1/pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f", size = 6984596, upload-time = "2025-07-01T09:16:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, -] - [[package]] name = "pillow" version = "12.1.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, - { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, - { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, - { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, - { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, - { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, - { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, - { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, - { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, - { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, - { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, - { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, - { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, @@ -373,13 +153,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, - { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, - { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, ] [[package]] @@ -400,42 +173,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -dependencies = [ - { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, - { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "packaging", marker = "python_full_version < '3.10'" }, - { name = "pluggy", marker = "python_full_version < '3.10'" }, - { name = "pygments", marker = "python_full_version < '3.10'" }, - { name = "tomli", marker = "python_full_version < '3.10'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, -] - [[package]] name = "pytest" version = "9.0.2" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] dependencies = [ - { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, - { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, - { name = "packaging", marker = "python_full_version >= '3.10'" }, - { name = "pluggy", marker = "python_full_version >= '3.10'" }, - { name = "pygments", marker = "python_full_version >= '3.10'" }, - { name = "tomli", marker = "python_full_version == '3.10.*'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ @@ -448,8 +195,7 @@ version = "4.4.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "charset-normalizer" }, - { name = "pillow", version = "11.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pillow", version = "12.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pillow" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/a7/4600cb1cfc975a06552e8927844ddcb8fd90217e9a6068f5c7aa76c3f221/reportlab-4.4.7.tar.gz", hash = "sha256:41e8287af965e5996764933f3e75e7f363c3b6f252ba172f9429e81658d7b170", size = 3714000, upload-time = "2025-12-21T11:50:11.336Z" } wheels = [ @@ -492,11 +238,9 @@ dependencies = [ [package.dev-dependencies] dev = [ - { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest" }, { name = "ruff" }, - { name = "setuptools", version = "82.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, - { name = "setuptools", version = "83.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "setuptools" }, { name = "ty" }, ] @@ -511,79 +255,15 @@ dev = [ { name = "ty", specifier = ">=0.0.53" }, ] -[[package]] -name = "setuptools" -version = "82.0.1" -source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version < '3.10'", -] -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, -] - [[package]] name = "setuptools" version = "83.0.0" source = { registry = "https://pypi.org/simple" } -resolution-markers = [ - "python_full_version >= '3.10'", -] sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" }, ] -[[package]] -name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, -] - [[package]] name = "ty" version = "0.0.60" @@ -608,12 +288,3 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/08/fdf4072d96c71b65fe98c9ca2ea5b3d2d0f6e20873bb2639e8c7827bd5f6/ty-0.0.60-py3-none-win_amd64.whl", hash = "sha256:15d83c3d793cb07840b8888c084cc2c49eb8acc29456a2fcdfbfd509c82526e5", size = 12249240, upload-time = "2026-07-16T10:18:08.701Z" }, { url = "https://files.pythonhosted.org/packages/bd/a2/83a496d717f712f894cf26690bcd9465ca23cd1c9139cac669d9cca45d14/ty-0.0.60-py3-none-win_arm64.whl", hash = "sha256:32e63228c3d21ddb192385aaf54501f19478be7b764f7572014d5110e53a9085", size = 11620140, upload-time = "2026-07-16T10:18:11.316Z" }, ] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, -]