From 19ad7a37048a9410d2316eb1784808e220a08032 Mon Sep 17 00:00:00 2001 From: dhvcc Date: Sun, 16 Nov 2025 16:34:39 +0100 Subject: [PATCH 01/10] WIP Pydantic v2, move old models to legacy, py3.14 --- .github/workflows/ci.yml | 2 +- README.md | 21 ++++- pyproject.toml | 5 +- rss_parser/__init__.py | 2 +- rss_parser/_parser.py | 4 + rss_parser/models/__init__.py | 35 ++++---- rss_parser/models/atom/atom.py | 7 +- rss_parser/models/atom/entry.py | 13 ++- rss_parser/models/atom/feed.py | 15 ++-- rss_parser/models/atom/person.py | 3 - rss_parser/models/atom/source.py | 3 - rss_parser/models/legacy/__init__.py | 32 ++++++++ rss_parser/models/legacy/atom/__init__.py | 3 + rss_parser/models/legacy/atom/atom.py | 15 ++++ rss_parser/models/legacy/atom/entry.py | 56 +++++++++++++ rss_parser/models/legacy/atom/feed.py | 61 ++++++++++++++ rss_parser/models/legacy/atom/person.py | 18 ++++ rss_parser/models/legacy/atom/source.py | 19 +++++ rss_parser/models/legacy/pydantic_proxy.py | 11 +++ rss_parser/models/legacy/rss/__init__.py | 3 + rss_parser/models/legacy/rss/channel.py | 82 +++++++++++++++++++ rss_parser/models/legacy/rss/image.py | 26 ++++++ rss_parser/models/legacy/rss/item.py | 48 +++++++++++ rss_parser/models/legacy/rss/rss.py | 15 ++++ rss_parser/models/legacy/rss/text_input.py | 23 ++++++ rss_parser/models/legacy/types/__init__.py | 5 ++ rss_parser/models/legacy/types/date.py | 41 ++++++++++ rss_parser/models/legacy/types/only_list.py | 23 ++++++ rss_parser/models/legacy/types/tag.py | 91 +++++++++++++++++++++ rss_parser/models/legacy/utils.py | 10 +++ rss_parser/models/rss/channel.py | 27 +++--- rss_parser/models/rss/image.py | 2 +- rss_parser/models/rss/item.py | 13 ++- rss_parser/models/rss/rss.py | 7 +- rss_parser/models/types/__init__.py | 5 ++ rss_parser/models/types/date.py | 25 +++--- rss_parser/models/types/only_list.py | 32 +++++--- rss_parser/models/types/tag.py | 57 ++++++------- rss_parser/pydantic_proxy.py | 12 +-- tests/test_parsing.py | 39 +++++++-- 40 files changed, 762 insertions(+), 149 deletions(-) create mode 100644 rss_parser/models/legacy/__init__.py create mode 100644 rss_parser/models/legacy/atom/__init__.py create mode 100644 rss_parser/models/legacy/atom/atom.py create mode 100644 rss_parser/models/legacy/atom/entry.py create mode 100644 rss_parser/models/legacy/atom/feed.py create mode 100644 rss_parser/models/legacy/atom/person.py create mode 100644 rss_parser/models/legacy/atom/source.py create mode 100644 rss_parser/models/legacy/pydantic_proxy.py create mode 100644 rss_parser/models/legacy/rss/__init__.py create mode 100644 rss_parser/models/legacy/rss/channel.py create mode 100644 rss_parser/models/legacy/rss/image.py create mode 100644 rss_parser/models/legacy/rss/item.py create mode 100644 rss_parser/models/legacy/rss/rss.py create mode 100644 rss_parser/models/legacy/rss/text_input.py create mode 100644 rss_parser/models/legacy/types/__init__.py create mode 100644 rss_parser/models/legacy/types/date.py create mode 100644 rss_parser/models/legacy/types/only_list.py create mode 100644 rss_parser/models/legacy/types/tag.py create mode 100644 rss_parser/models/legacy/utils.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c4903b..bf3f425 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: max-parallel: 6 matrix: os: [ "ubuntu-latest", "windows-latest", "macos-latest" ] - python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13" ] + python-version: [ "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ] runs-on: ${{ matrix.os }} diff --git a/README.md b/README.md index 6e0c4e1..3b212a0 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,20 @@ pip install dist/*.whl - Models for RSS-specific schemas have been moved from `rss_parser.models` to `rss_parser.models.rss`. Generic types remain unchanged - Date parsing has been improved and now uses pydantic's `validator` instead of `email.utils`, producing better datetime objects where it previously defaulted to `str` +## Legacy Models + +Pydantic v1-based models are still available under `rss_parser.models.legacy`. They retain the previous behaviour and re-export the `import_v1_pydantic` helper as `rss_parser.models.legacy.pydantic_proxy.import_v1_pydantic`. You can continue to use them by pointing your parser at the legacy schema: + +```python +from rss_parser import RSSParser +from rss_parser.models.legacy.rss import RSS as LegacyRSS + +class LegacyRSSParser(RSSParser): + schema = LegacyRSS +``` + +Tests in this repository run against both the v2 and legacy models to ensure parity. + ## Usage ### Quickstart @@ -163,18 +177,17 @@ If you don't want to deal with these conditions and want to parse something **al ```python from typing import Optional +from pydantic import Field + from rss_parser.models.rss.item import Item from rss_parser.models.types.only_list import OnlyList from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() ... class OptionalChannelElementsMixin(...): ... - items: Optional[OnlyList[Tag[Item]]] = pydantic.Field(alias="item", default=[]) + items: Optional[OnlyList[Tag[Item]]] = Field(alias="item", default_factory=list) ``` ### Tag Field diff --git a/pyproject.toml b/pyproject.toml index df986ef..f093c46 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,7 @@ packages = [{ include = "rss_parser" }, { include = "rss_parser/py.typed" }] [tool.poetry.dependencies] python = "^3.9" -pydantic = ">1.9" +pydantic = "<3.0" xmltodict = "^0.13.0" types-xmltodict = "^0.14.0.20241009" @@ -85,6 +85,9 @@ select = [ "RUF", # ruff ] +[tool.ruff.lint.pep8-naming] +ignore-names = ["LegacyRSS"] + [tool.ruff.per-file-ignores] "tests/**.py" = [ "S101", # Use of assert detected diff --git a/rss_parser/__init__.py b/rss_parser/__init__.py index 88cad72..1ae2861 100644 --- a/rss_parser/__init__.py +++ b/rss_parser/__init__.py @@ -1,3 +1,3 @@ from ._parser import AtomParser, BaseParser, RSSParser -__all__ = ("BaseParser", "AtomParser", "RSSParser") +__all__ = ("AtomParser", "BaseParser", "RSSParser") diff --git a/rss_parser/_parser.py b/rss_parser/_parser.py index e95953c..c00e663 100644 --- a/rss_parser/_parser.py +++ b/rss_parser/_parser.py @@ -48,6 +48,10 @@ def parse( if root_key: root = root.get(root_key, root) + if hasattr(schema, "model_validate"): + return schema.model_validate(root) + + # Pydantic v1 only return schema.parse_obj(root) diff --git a/rss_parser/models/__init__.py b/rss_parser/models/__init__.py index e804326..a885891 100644 --- a/rss_parser/models/__init__.py +++ b/rss_parser/models/__init__.py @@ -1,32 +1,25 @@ -""" -Models created according to https://www.rssboard.org/rss-specification. +from __future__ import annotations -Some types and validation may be a bit custom to account for broken standards in some RSS feeds. -""" - -from json import loads -from typing import TYPE_CHECKING +from pydantic import BaseModel, ConfigDict from rss_parser.models.utils import camel_case -from rss_parser.pydantic_proxy import import_v1_pydantic - -if TYPE_CHECKING: - from pydantic import v1 as pydantic -else: - pydantic = import_v1_pydantic() -class XMLBaseModel(pydantic.BaseModel): - class Config: - alias_generator = camel_case +class XMLBaseModel(BaseModel): + model_config = ConfigDict(alias_generator=camel_case) - def json_plain(self, **kw): + def json_plain(self, **kwargs) -> str: """ - Run pydantic's json with custom encoder to encode Tags as only content. + Serialize the model while flattening Tag instances into their content. """ from rss_parser.models.types.tag import Tag # noqa: PLC0415 - return self.json(models_as_dict=False, encoder=Tag.flatten_tag_encoder, **kw) + return self.model_dump_json(fallback=Tag.flatten_tag_encoder, **kwargs) + + def dict_plain(self, **kwargs): + from rss_parser.models.types.tag import Tag # noqa: PLC0415 + + return self.model_dump(mode="json", fallback=Tag.flatten_tag_encoder, **kwargs) + - def dict_plain(self, **kw): - return loads(self.json_plain(**kw)) +__all__ = ("XMLBaseModel",) diff --git a/rss_parser/models/atom/atom.py b/rss_parser/models/atom/atom.py index 12f84a7..38f6327 100644 --- a/rss_parser/models/atom/atom.py +++ b/rss_parser/models/atom/atom.py @@ -1,15 +1,14 @@ from typing import Optional +from pydantic import Field + from rss_parser.models import XMLBaseModel from rss_parser.models.atom.feed import Feed from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class Atom(XMLBaseModel): """Atom 1.0""" - version: Optional[Tag[str]] = pydantic.Field(alias="@version") + version: Optional[Tag[str]] = Field(alias="@version", default=None) feed: Tag[Feed] diff --git a/rss_parser/models/atom/entry.py b/rss_parser/models/atom/entry.py index abbf50c..9c567a3 100644 --- a/rss_parser/models/atom/entry.py +++ b/rss_parser/models/atom/entry.py @@ -1,13 +1,12 @@ from typing import Optional +from pydantic import Field + from rss_parser.models import XMLBaseModel from rss_parser.models.atom.person import Person from rss_parser.models.types.date import DateTimeOrStr from rss_parser.models.types.only_list import OnlyList from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class RequiredAtomEntryMixin(XMLBaseModel): @@ -22,10 +21,10 @@ class RequiredAtomEntryMixin(XMLBaseModel): class RecommendedAtomEntryMixin(XMLBaseModel): - authors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="author", default=[]) + authors: Optional[OnlyList[Tag[Person]]] = Field(alias="author", default_factory=list) "Entry authors." - links: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="link", default=[]) + links: Optional[OnlyList[Tag[str]]] = Field(alias="link", default_factory=list) "The URL of the entry." content: Optional[Tag[str]] = None @@ -36,10 +35,10 @@ class RecommendedAtomEntryMixin(XMLBaseModel): class OptionalAtomEntryMixin(XMLBaseModel): - categories: Optional[OnlyList[Tag[dict]]] = pydantic.Field(alias="category", default=[]) + categories: Optional[OnlyList[Tag[dict]]] = Field(alias="category", default_factory=list) "Specifies a categories that the entry belongs to." - contributors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="contributor", default=[]) + contributors: Optional[OnlyList[Tag[Person]]] = Field(alias="contributor", default_factory=list) "Entry contributors." rights: Optional[Tag[str]] = None diff --git a/rss_parser/models/atom/feed.py b/rss_parser/models/atom/feed.py index 4219c28..8b5a3c7 100644 --- a/rss_parser/models/atom/feed.py +++ b/rss_parser/models/atom/feed.py @@ -1,14 +1,13 @@ from typing import Optional +from pydantic import Field + from rss_parser.models import XMLBaseModel from rss_parser.models.atom.entry import Entry from rss_parser.models.atom.person import Person from rss_parser.models.types.date import DateTimeOrStr from rss_parser.models.types.only_list import OnlyList from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class RequiredAtomFeedMixin(XMLBaseModel): @@ -23,21 +22,21 @@ class RequiredAtomFeedMixin(XMLBaseModel): class RecommendedAtomFeedMixin(XMLBaseModel): - authors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="author", default=[]) + authors: Optional[OnlyList[Tag[Person]]] = Field(alias="author", default_factory=list) "Names one author of the feed. A feed may have multiple author elements." - links: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="link", default=[]) + links: Optional[OnlyList[Tag[str]]] = Field(alias="link", default_factory=list) "The URL to the feed. A feed may have multiple link elements." class OptionalAtomFeedMixin(XMLBaseModel): - entries: Optional[OnlyList[Tag[Entry]]] = pydantic.Field(alias="entry", default=[]) + entries: Optional[OnlyList[Tag[Entry]]] = Field(alias="entry", default_factory=list) "The entries in the feed. A feed may have multiple entry elements." - categories: Optional[OnlyList[Tag[dict]]] = pydantic.Field(alias="category", default=[]) + categories: Optional[OnlyList[Tag[dict]]] = Field(alias="category", default_factory=list) "Specifies a categories that the feed belongs to. The feed may have multiple categories elements." - contributors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="contributor", default=[]) + contributors: Optional[OnlyList[Tag[Person]]] = Field(alias="contributor", default_factory=list) "Feed contributors." generator: Optional[Tag[str]] = None diff --git a/rss_parser/models/atom/person.py b/rss_parser/models/atom/person.py index 97a9ef7..be77176 100644 --- a/rss_parser/models/atom/person.py +++ b/rss_parser/models/atom/person.py @@ -2,9 +2,6 @@ from rss_parser.models import XMLBaseModel from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class Person(XMLBaseModel): diff --git a/rss_parser/models/atom/source.py b/rss_parser/models/atom/source.py index 9dab9a3..0263b0d 100644 --- a/rss_parser/models/atom/source.py +++ b/rss_parser/models/atom/source.py @@ -3,9 +3,6 @@ from rss_parser.models import XMLBaseModel from rss_parser.models.types.date import DateTimeOrStr from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class Source(XMLBaseModel): diff --git a/rss_parser/models/legacy/__init__.py b/rss_parser/models/legacy/__init__.py new file mode 100644 index 0000000..09215e0 --- /dev/null +++ b/rss_parser/models/legacy/__init__.py @@ -0,0 +1,32 @@ +""" +Models created according to https://www.rssboard.org/rss-specification. + +Some types and validation may be a bit custom to account for broken standards in some RSS feeds. +""" + +from json import loads +from typing import TYPE_CHECKING + +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.utils import camel_case + +if TYPE_CHECKING: + from pydantic import v1 as pydantic +else: + pydantic = import_v1_pydantic() + + +class XMLBaseModel(pydantic.BaseModel): + class Config: + alias_generator = camel_case + + def json_plain(self, **kw): + """ + Run pydantic's json with custom encoder to encode Tags as only content. + """ + from rss_parser.models.legacy.types.tag import Tag # noqa: PLC0415 + + return self.json(models_as_dict=False, encoder=Tag.flatten_tag_encoder, **kw) + + def dict_plain(self, **kw): + return loads(self.json_plain(**kw)) diff --git a/rss_parser/models/legacy/atom/__init__.py b/rss_parser/models/legacy/atom/__init__.py new file mode 100644 index 0000000..187c4b1 --- /dev/null +++ b/rss_parser/models/legacy/atom/__init__.py @@ -0,0 +1,3 @@ +from .atom import Atom + +__all__ = ("Atom",) diff --git a/rss_parser/models/legacy/atom/atom.py b/rss_parser/models/legacy/atom/atom.py new file mode 100644 index 0000000..48a3240 --- /dev/null +++ b/rss_parser/models/legacy/atom/atom.py @@ -0,0 +1,15 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.atom.feed import Feed +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class Atom(XMLBaseModel): + """Atom 1.0""" + + version: Optional[Tag[str]] = pydantic.Field(alias="@version") + feed: Tag[Feed] diff --git a/rss_parser/models/legacy/atom/entry.py b/rss_parser/models/legacy/atom/entry.py new file mode 100644 index 0000000..ed73fb9 --- /dev/null +++ b/rss_parser/models/legacy/atom/entry.py @@ -0,0 +1,56 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.atom.person import Person +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.types.date import DateTimeOrStr +from rss_parser.models.legacy.types.only_list import OnlyList +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class RequiredAtomEntryMixin(XMLBaseModel): + id: Tag[str] + "Identifier for the entry." + + title: Tag[str] + "The title of the entry." + + updated: Tag[DateTimeOrStr] + "Indicates when the entry was updated." + + +class RecommendedAtomEntryMixin(XMLBaseModel): + authors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="author", default=[]) + "Entry authors." + + links: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="link", default=[]) + "The URL of the entry." + + content: Optional[Tag[str]] = None + "The main content of the entry." + + summary: Optional[Tag[str]] = None + "Conveys a short summary, abstract, or excerpt of the entry. Some feeds use this tag as the main content." + + +class OptionalAtomEntryMixin(XMLBaseModel): + categories: Optional[OnlyList[Tag[dict]]] = pydantic.Field(alias="category", default=[]) + "Specifies a categories that the entry belongs to." + + contributors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="contributor", default=[]) + "Entry contributors." + + rights: Optional[Tag[str]] = None + "The copyright of the entry." + + published: Optional[Tag[DateTimeOrStr]] = None + "Indicates when the entry was published." + + source: Optional[Tag[str]] = None + "Contains metadata from the source feed if this entry is a copy." + + +class Entry(RequiredAtomEntryMixin, RecommendedAtomEntryMixin, OptionalAtomEntryMixin, XMLBaseModel): + """https://validator.w3.org/feed/docs/atom.html""" diff --git a/rss_parser/models/legacy/atom/feed.py b/rss_parser/models/legacy/atom/feed.py new file mode 100644 index 0000000..4f4af91 --- /dev/null +++ b/rss_parser/models/legacy/atom/feed.py @@ -0,0 +1,61 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.atom.entry import Entry +from rss_parser.models.legacy.atom.person import Person +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.types.date import DateTimeOrStr +from rss_parser.models.legacy.types.only_list import OnlyList +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class RequiredAtomFeedMixin(XMLBaseModel): + id: Tag[str] + "Identifies the feed using a universally unique and permanent URI." + + title: Tag[str] + "Contains a human readable title for the feed." + + updated: Tag[DateTimeOrStr] + "Indicates the last time the feed was modified in a significant way." + + +class RecommendedAtomFeedMixin(XMLBaseModel): + authors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="author", default=[]) + "Names one author of the feed. A feed may have multiple author elements." + + links: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="link", default=[]) + "The URL to the feed. A feed may have multiple link elements." + + +class OptionalAtomFeedMixin(XMLBaseModel): + entries: Optional[OnlyList[Tag[Entry]]] = pydantic.Field(alias="entry", default=[]) + "The entries in the feed. A feed may have multiple entry elements." + + categories: Optional[OnlyList[Tag[dict]]] = pydantic.Field(alias="category", default=[]) + "Specifies a categories that the feed belongs to. The feed may have multiple categories elements." + + contributors: Optional[OnlyList[Tag[Person]]] = pydantic.Field(alias="contributor", default=[]) + "Feed contributors." + + generator: Optional[Tag[str]] = None + "Identifies the software used to generate the feed, for debugging and other purposes." + + icon: Optional[Tag[str]] = None + "Identifies a small image which provides iconic visual identification for the feed. Icons should be square." + + logo: Optional[Tag[str]] = None + "Identifies a larger image which provides visual identification for the feed. \ + Images should be twice as wide as they are tall." + + rights: Optional[Tag[str]] = None + "The copyright of the feed." + + subtitle: Optional[Tag[str]] = None + "Contains a human readable description or subtitle for the feed." + + +class Feed(RequiredAtomFeedMixin, RecommendedAtomFeedMixin, OptionalAtomFeedMixin, XMLBaseModel): + """https://validator.w3.org/feed/docs/atom.html""" diff --git a/rss_parser/models/legacy/atom/person.py b/rss_parser/models/legacy/atom/person.py new file mode 100644 index 0000000..83b59cd --- /dev/null +++ b/rss_parser/models/legacy/atom/person.py @@ -0,0 +1,18 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class Person(XMLBaseModel): + name: Tag[str] + "Conveys a human-readable name for the person." + + uri: Optional[Tag[str]] = None + "Contains a home page for the person." + + email: Optional[Tag[str]] = None + "Contains an email address for the person." diff --git a/rss_parser/models/legacy/atom/source.py b/rss_parser/models/legacy/atom/source.py new file mode 100644 index 0000000..0b00165 --- /dev/null +++ b/rss_parser/models/legacy/atom/source.py @@ -0,0 +1,19 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.types.date import DateTimeOrStr +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class Source(XMLBaseModel): + id: Optional[Tag[str]] = None + "Source id." + + title: Optional[Tag[str]] = None + "Title of the source." + + updated: Optional[Tag[DateTimeOrStr]] = None + "When source was updated." diff --git a/rss_parser/models/legacy/pydantic_proxy.py b/rss_parser/models/legacy/pydantic_proxy.py new file mode 100644 index 0000000..5b5ae8a --- /dev/null +++ b/rss_parser/models/legacy/pydantic_proxy.py @@ -0,0 +1,11 @@ +from importlib import import_module +from importlib.metadata import version + +_pydantic_version = version("pydantic") + + +def import_v1_pydantic(relative_submodule_path: str = ""): + if _pydantic_version[0] == "2": + return import_module("pydantic.v1" + relative_submodule_path) + else: + return import_module("pydantic" + relative_submodule_path) diff --git a/rss_parser/models/legacy/rss/__init__.py b/rss_parser/models/legacy/rss/__init__.py new file mode 100644 index 0000000..67d9354 --- /dev/null +++ b/rss_parser/models/legacy/rss/__init__.py @@ -0,0 +1,3 @@ +from .rss import RSS + +__all__ = ("RSS",) diff --git a/rss_parser/models/legacy/rss/channel.py b/rss_parser/models/legacy/rss/channel.py new file mode 100644 index 0000000..1d35594 --- /dev/null +++ b/rss_parser/models/legacy/rss/channel.py @@ -0,0 +1,82 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.rss.image import Image +from rss_parser.models.legacy.rss.item import Item +from rss_parser.models.legacy.rss.text_input import TextInput +from rss_parser.models.legacy.types.date import DateTimeOrStr +from rss_parser.models.legacy.types.only_list import OnlyList +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class RequiredChannelElementsMixin(XMLBaseModel): + """https://www.rssboard.org/rss-specification#requiredChannelElements.""" + + title: Tag[str] # GoUpstate.com News Headlines + "The name of the channel. It's how people refer to your service. If you have an HTML website that contains the same information as your RSS file, the title of your channel should be the same as the title of your website." # noqa + + link: Tag[str] # http://www.goupstate.com/ + "The URL to the HTML website corresponding to the channel." + + description: Tag[str] # The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site. + "Phrase or sentence describing the channel." + + +class OptionalChannelElementsMixin(XMLBaseModel): + """https://www.rssboard.org/rss-specification#optionalChannelElements.""" + + items: Optional[OnlyList[Tag[Item]]] = pydantic.Field(alias="item", default=[]) + + language: Optional[Tag[str]] = None # en-us + "The language the channel is written in. This allows aggregators to group all Italian language sites, for example, on a single page." # noqa + + copyright: Optional[Tag[str]] = None # Copyright 2002, Spartanburg Herald-Journal + "Copyright notice for content in the channel." + + "Email address for person responsible for editorial content." + + web_master: Optional[Tag[str]] = None # betty@herald.com (Betty Guernsey) + "Email address for person responsible for technical issues relating to channel." + + pub_date: Optional[Tag[DateTimeOrStr]] = None # Sat, 07 Sep 2002 00:00:01 GMT + "The publication date for the content in the channel. For example, the New York Times publishes on a daily basis, the publication date flips once every 24 hours. That's when the pubDate of the channel changes. All date-times in RSS conform to the Date and Time Specification of RFC 822, with the exception that the year may be expressed with two characters or four characters (four preferred)." # noqa + + last_build_date: Optional[Tag[DateTimeOrStr]] = None # Sat, 07 Sep 2002 09:42:31 GMT + "The last time the content of the channel changed." + + categories: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="category", default=[]) + "Specify one or more categories that the channel belongs to. Follows the same rules as the -level category element." # noqa + + generator: Optional[Tag[str]] = None # MightyInHouse Content System v2.3 + "A string indicating the program used to generate the channel." + + docs: Optional[Tag[str]] = None # https://www.rssboard.org/rss-specification + "A URL that points to the documentation for the format used in the RSS file. It's probably a pointer to this page. It's for people who might stumble across an RSS file on a Web server 25 years from now and wonder what it is." # noqa + + cloud: Optional[Tag[str]] = None # + "Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds." # noqa + + ttl: Optional[Tag[str]] = None # 60 + "ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before refreshing from the source." # noqa + + image: Optional[Tag[Image]] = None + "Specifies a GIF, JPEG or PNG image that can be displayed with the channel." + + rating: Optional[Tag[TextInput]] = None + "The PICS rating for the channel." + + text_input: Optional[Tag[str]] = None + "Specifies a text input box that can be displayed with the channel." + + skip_hours: Optional[Tag[str]] = None + "A hint for aggregators telling them which hours they can skip. This element contains up to 24 sub-elements whose value is a number between 0 and 23, representing a time in GMT, when aggregators, if they support the feature, may not read the channel on hours listed in the element. The hour beginning at midnight is hour zero." # noqa + + skip_days: Optional[Tag[str]] = None + "A hint for aggregators telling them which days they can skip. This element contains up to seven sub-elements whose value is Monday, Tuesday, Wednesday, Thursday, Friday, Saturday or Sunday. Aggregators may not read the channel during days listed in the element." # noqa + + +class Channel(RequiredChannelElementsMixin, OptionalChannelElementsMixin, XMLBaseModel): + pass diff --git a/rss_parser/models/legacy/rss/image.py b/rss_parser/models/legacy/rss/image.py new file mode 100644 index 0000000..2b91436 --- /dev/null +++ b/rss_parser/models/legacy/rss/image.py @@ -0,0 +1,26 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.types.tag import Tag + + +class Image(XMLBaseModel): + """https://www.rssboard.org/rss-specification#ltimagegtSubelementOfLtchannelgt.""" + + url: Tag[str] + "The URL of a GIF, JPEG or PNG image that represents the channel." + + title: Tag[str] + "Describes the image, it's used in the ALT attribute of the HTML tag when the channel is rendered in HTML." + + link: Tag[str] + "The URL of the site, when the channel is rendered, the image is a link to the site. (Note, in practice the image and <link> should have the same value as the channel's <title> and <link>." # noqa + + width: Optional[Tag[int]] = None + "Number, indicating the width of the image in pixels." + + height: Optional[Tag[int]] = None + "Number, indicating the height of the image in pixels." + + description: Optional[Tag[str]] = None + "Contains text that is included in the TITLE attribute of the link formed around the image in the HTML rendering." diff --git a/rss_parser/models/legacy/rss/item.py b/rss_parser/models/legacy/rss/item.py new file mode 100644 index 0000000..82e9f1b --- /dev/null +++ b/rss_parser/models/legacy/rss/item.py @@ -0,0 +1,48 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.types.only_list import OnlyList +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class RequiredItemElementsMixin(XMLBaseModel): + title: Tag[str] # Venice Film Festival Tries to Quit Sinking + "The title of the item." + + links: OnlyList[Tag[str]] = pydantic.Field(alias="link") # http://nytimes.com/2004/12/07FEST.html + "The URL of the item." + + description: Tag[str] # <description>Some of the most heated chatter at the Venice Film Festival this week was + # about the way that the arrival of the stars at the Palazzo del Cinema was being staged.</description> + "The item synopsis." + + +class OptionalItemElementsMixin(XMLBaseModel): + author: Optional[Tag[str]] = None + "Email address of the author of the item." + + categories: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="category", default=[]) + "Includes the item in one or more categories." + + comments: Optional[Tag[str]] = None + "URL of a page for comments relating to the item." + + enclosures: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="enclosure", default=[]) + # enclosure: Optional[OnlyList[Tag[str]]] = None + "Describes a media object that is attached to the item.\nCan be a list -> https://validator.w3.org/feed/docs/warning/DuplicateEnclosure.html" + + guid: Optional[Tag[str]] = None + "A string that uniquely identifies the item." + + pub_date: Optional[Tag[str]] = None + "Indicates when the item was published." + + source: Optional[Tag[str]] = None + "The RSS channel that the item came from." + + +class Item(RequiredItemElementsMixin, OptionalItemElementsMixin, XMLBaseModel): + """https://www.rssboard.org/rss-specification#hrelementsOfLtitemgt.""" diff --git a/rss_parser/models/legacy/rss/rss.py b/rss_parser/models/legacy/rss/rss.py new file mode 100644 index 0000000..8bd13a4 --- /dev/null +++ b/rss_parser/models/legacy/rss/rss.py @@ -0,0 +1,15 @@ +from typing import Optional + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.rss.channel import Channel +from rss_parser.models.legacy.types.tag import Tag + +pydantic = import_v1_pydantic() + + +class RSS(XMLBaseModel): + """RSS 2.0.""" + + version: Optional[Tag[str]] = pydantic.Field(alias="@version") + channel: Tag[Channel] diff --git a/rss_parser/models/legacy/rss/text_input.py b/rss_parser/models/legacy/rss/text_input.py new file mode 100644 index 0000000..37be47c --- /dev/null +++ b/rss_parser/models/legacy/rss/text_input.py @@ -0,0 +1,23 @@ +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.types.tag import Tag + + +class TextInput(XMLBaseModel): + """ + The purpose of the <textInput> element is something of a mystery. You can use it to specify a search engine box. + Or to allow a reader to provide feedback. Most aggregators ignore it. + + https://www.rssboard.org/rss-specification#lttextinputgtSubelementOfLtchannelgt + """ + + title: Tag[str] + "The label of the Submit button in the text input area." + + description: Tag[str] + "Explains the text input area." + + name: Tag[str] + "The name of the text object in the text input area." + + link: Tag[str] + "The URL of the CGI script that processes text input requests." diff --git a/rss_parser/models/legacy/types/__init__.py b/rss_parser/models/legacy/types/__init__.py new file mode 100644 index 0000000..0794ecc --- /dev/null +++ b/rss_parser/models/legacy/types/__init__.py @@ -0,0 +1,5 @@ +from rss_parser.models.legacy.types.date import DateTimeOrStr +from rss_parser.models.legacy.types.only_list import OnlyList +from rss_parser.models.legacy.types.tag import Tag + +__all__ = ("DateTimeOrStr", "OnlyList", "Tag") diff --git a/rss_parser/models/legacy/types/date.py b/rss_parser/models/legacy/types/date.py new file mode 100644 index 0000000..edf6b13 --- /dev/null +++ b/rss_parser/models/legacy/types/date.py @@ -0,0 +1,41 @@ +from datetime import datetime +from email.utils import parsedate_to_datetime +from typing import Union + +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic + +pydantic_validators = import_v1_pydantic(".validators") + + +class DateTimeOrStr(datetime): + @classmethod + def __get_validators__(cls): + yield validate_dt_or_str + + @classmethod + def __get_pydantic_json_schema__(cls, field_schema): + field_schema.update( + examples=[datetime(1970, 1, 1, 0, 0, 0)], + ) + + @classmethod + def validate(cls, v): + return validate_dt_or_str(v) + + def __repr__(self): + return f"DateTimeOrStp({super().__repr__()})" + + +def validate_dt_or_str(value: str) -> Union[datetime, str]: + # Try to parse standard (RFC 822) + try: + return parsedate_to_datetime(value) + except (ValueError, TypeError): # https://github.com/python/cpython/issues/74866 + pass + # Try ISO or timestamp + try: + return pydantic_validators.parse_datetime(value) + except ValueError: + pass + + return value diff --git a/rss_parser/models/legacy/types/only_list.py b/rss_parser/models/legacy/types/only_list.py new file mode 100644 index 0000000..21679b1 --- /dev/null +++ b/rss_parser/models/legacy/types/only_list.py @@ -0,0 +1,23 @@ +from typing import Generic, TypeVar, Union + +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic + +pydantic_validators = import_v1_pydantic(".validators") + +T = TypeVar("T") + + +class OnlyList(list, Generic[T]): + @classmethod + def __get_validators__(cls): + yield cls.validate + yield pydantic_validators.list_validator + + @classmethod + def validate(cls, v: Union[dict, list]): + if isinstance(v, list): + return cls(v) + return cls([v]) + + def __repr__(self): + return f"OnlyList({super().__repr__()})" diff --git a/rss_parser/models/legacy/types/tag.py b/rss_parser/models/legacy/types/tag.py new file mode 100644 index 0000000..159ecc6 --- /dev/null +++ b/rss_parser/models/legacy/types/tag.py @@ -0,0 +1,91 @@ +from copy import deepcopy +from json import loads +from typing import TYPE_CHECKING, Generic, Optional, TypeVar, Union + +from rss_parser.models.legacy import XMLBaseModel +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic +from rss_parser.models.legacy.utils import snake_case + +if TYPE_CHECKING: + from pydantic.v1 import generics as pydantic_generics + from pydantic.v1 import json as pydantic_json +else: + pydantic = import_v1_pydantic() + pydantic_generics = import_v1_pydantic(".generics") + pydantic_json = import_v1_pydantic(".json") + +T = TypeVar("T") + + +class Tag(pydantic_generics.GenericModel, Generic[T]): + """ + >>> from rss_parser.models.legacy import XMLBaseModel + >>> from rss_parser.models.legacy.types.tag import Tag + >>> class Model(XMLBaseModel): + ... width: Tag[int] + ... category: Tag[str] + >>> m = Model( + ... width=48, + ... category={"@someAttribute": "https://example.com", "#text": "valid string"}, + ... ) + >>> # Content value is an integer, as per the generic type + >>> m.width.content + 48 + >>> type(m.width), type(m.width.content) + (<class 'rss_parser.models.legacy.rss.image.Tag[int]'>, <class 'int'>) + >>> # The attributes are empty by default + >>> m.width.attributes + {} + >>> # But are populated when provided. + >>> # Note that the @ symbol is trimmed from the beggining and name is convert to snake_case + >>> m.category.attributes + {'some_attribute': 'https://example.com'} + >>> # Generic argument types are handled by pydantic - let's try to provide a string for a Tag[int] number + >>> m = Model(width="not_a_number", category="valid_string") # doctest: +IGNORE_EXCEPTION_DETAIL + Traceback (most recent call last): + ... + ValidationError: 1 validation error for Model + width -> content + value is not a valid integer (type=type_error.integer) + """ + + # Optional in case of self-closing tags + content: Optional[T] + attributes: dict + + def __getattr__(self, item): + """Forward default getattr for content for simplicity.""" + return getattr(self.content, item) + + def __getitem__(self, key): + return self.content[key] + + def __setitem__(self, key, value): + self.content[key] = value + + @classmethod + def __get_validators__(cls): + yield cls.pre_convert + yield cls.validate + + @classmethod + def pre_convert(cls, v: Union[T, dict], **kwargs): # noqa + """Used to split tag's text with other xml attributes.""" + if isinstance(v, dict): + data = deepcopy(v) + attributes = {snake_case(k.lstrip("@")): v for k, v in data.items() if k.startswith("@")} + content = data.pop("#text", data) if not len(attributes) == len(data) else None + return {"content": content, "attributes": attributes} + return {"content": v, "attributes": {}} + + @classmethod + def flatten_tag_encoder(cls, v): + """Encoder that translates Tag objects (dict) to plain .content values (T).""" + bases = v.__class__.__bases__ + if XMLBaseModel in bases: + # Can't pass encoder to .dict :/ + return loads(v.json_plain()) + if cls in bases: + return v.content + + return pydantic_json.pydantic_encoder(v) diff --git a/rss_parser/models/legacy/utils.py b/rss_parser/models/legacy/utils.py new file mode 100644 index 0000000..5bef900 --- /dev/null +++ b/rss_parser/models/legacy/utils.py @@ -0,0 +1,10 @@ +from re import sub + + +def camel_case(s: str): + s = sub(r"([_\-])+", " ", s).title().replace(" ", "") + return "".join([s[0].lower(), s[1:]]) + + +def snake_case(s: str): + return sub(r"(?<!^)(?=[A-Z])", "_", s).lower() diff --git a/rss_parser/models/rss/channel.py b/rss_parser/models/rss/channel.py index bd166fe..c9bfeb0 100644 --- a/rss_parser/models/rss/channel.py +++ b/rss_parser/models/rss/channel.py @@ -1,5 +1,7 @@ from typing import Optional +from pydantic import Field + from rss_parser.models import XMLBaseModel from rss_parser.models.rss.image import Image from rss_parser.models.rss.item import Item @@ -7,16 +9,13 @@ from rss_parser.models.types.date import DateTimeOrStr from rss_parser.models.types.only_list import OnlyList from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class RequiredChannelElementsMixin(XMLBaseModel): """https://www.rssboard.org/rss-specification#requiredChannelElements.""" title: Tag[str] # GoUpstate.com News Headlines - "The name of the channel. It's how people refer to your service. If you have an HTML website that contains " "the same information as your RSS file, the title of your channel should be the same as the title of your " "website." # noqa + "The name of the channel. It's how people refer to your service. If you have an HTML website that contains the same information as your RSS file, the title of your channel should be the same as the title of your website." # noqa link: Tag[str] # http://www.goupstate.com/ "The URL to the HTML website corresponding to the channel." @@ -28,10 +27,10 @@ class RequiredChannelElementsMixin(XMLBaseModel): class OptionalChannelElementsMixin(XMLBaseModel): """https://www.rssboard.org/rss-specification#optionalChannelElements.""" - items: Optional[OnlyList[Tag[Item]]] = pydantic.Field(alias="item", default=[]) + items: Optional[OnlyList[Tag[Item]]] = Field(alias="item", default_factory=list) language: Optional[Tag[str]] = None # en-us - "The language the channel is written in. This allows aggregators to group all Italian language sites, " "for example, on a single page." # noqa + "The language the channel is written in. This allows aggregators to group all Italian language sites, for example, on a single page." # noqa copyright: Optional[Tag[str]] = None # Copyright 2002, Spartanburg Herald-Journal "Copyright notice for content in the channel." @@ -42,25 +41,25 @@ class OptionalChannelElementsMixin(XMLBaseModel): "Email address for person responsible for technical issues relating to channel." pub_date: Optional[Tag[DateTimeOrStr]] = None # Sat, 07 Sep 2002 00:00:01 GMT - "The publication date for the content in the channel. For example, the New York Times publishes on a daily " "basis, the publication date flips once every 24 hours. That's when the pubDate of the channel changes. All " "date-times in RSS conform to the Date and Time Specification of RFC 822, with the exception that the year " "may be expressed with two characters or four characters (four preferred)." # noqa + "The publication date for the content in the channel. For example, the New York Times publishes on a daily basis, the publication date flips once every 24 hours. That's when the pubDate of the channel changes. All date-times in RSS conform to the Date and Time Specification of RFC 822, with the exception that the year may be expressed with two characters or four characters (four preferred)." # noqa last_build_date: Optional[Tag[DateTimeOrStr]] = None # Sat, 07 Sep 2002 09:42:31 GMT "The last time the content of the channel changed." - categories: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="category", default=[]) - "Specify one or more categories that the channel belongs to. Follows the same rules as the <item.py>-level " "category element." # noqa + categories: Optional[OnlyList[Tag[str]]] = Field(alias="category", default_factory=list) + "Specify one or more categories that the channel belongs to. Follows the same rules as the <item.py>-level category element." # noqa generator: Optional[Tag[str]] = None # MightyInHouse Content System v2.3 "A string indicating the program used to generate the channel." docs: Optional[Tag[str]] = None # https://www.rssboard.org/rss-specification - "A URL that points to the documentation for the format used in the RSS file. It's probably a pointer to this " "page. It's for people who might stumble across an RSS file on a Web server 25 years from now and wonder what " "it is." # noqa + "A URL that points to the documentation for the format used in the RSS file. It's probably a pointer to this page. It's for people who might stumble across an RSS file on a Web server 25 years from now and wonder what it is." # noqa cloud: Optional[Tag[str]] = None # <cloud domain="rpc.sys.com" protocol="soap"/> - "Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight " "publish-subscribe protocol for RSS feeds." # noqa + "Allows processes to register with a cloud to be notified of updates to the channel, implementing a lightweight publish-subscribe protocol for RSS feeds." # noqa ttl: Optional[Tag[str]] = None # 60 - "ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before " "refreshing from the source." # noqa + "ttl stands for time to live. It's a number of minutes that indicates how long a channel can be cached before refreshing from the source." # noqa image: Optional[Tag[Image]] = None "Specifies a GIF, JPEG or PNG image that can be displayed with the channel." @@ -72,10 +71,10 @@ class OptionalChannelElementsMixin(XMLBaseModel): "Specifies a text input box that can be displayed with the channel." skip_hours: Optional[Tag[str]] = None - "A hint for aggregators telling them which hours they can skip. This element contains up to 24 <hour> " "sub-elements whose value is a number between 0 and 23, representing a time in GMT, when aggregators, if " "they support the feature, may not read the channel on hours listed in the <skipHours> element. The hour " "beginning at midnight is hour zero." # noqa + "A hint for aggregators telling them which hours they can skip. This element contains up to 24 <hour> sub-elements whose value is a number between 0 and 23, representing a time in GMT, when aggregators, if they support the feature, may not read the channel on hours listed in the <skipHours> element. The hour beginning at midnight is hour zero." # noqa skip_days: Optional[Tag[str]] = None - "A hint for aggregators telling them which days they can skip. This element contains up to seven <day> " "sub-elements whose value is Monday, Tuesday, Wednesday, Thursday, Friday, Saturday or Sunday. Aggregators " "may not read the channel during days listed in the <skipDays> element." # noqa + "A hint for aggregators telling them which days they can skip. This element contains up to seven <day> sub-elements whose value is Monday, Tuesday, Wednesday, Thursday, Friday, Saturday or Sunday. Aggregators may not read the channel during days listed in the <skipDays> element." # noqa class Channel(RequiredChannelElementsMixin, OptionalChannelElementsMixin, XMLBaseModel): diff --git a/rss_parser/models/rss/image.py b/rss_parser/models/rss/image.py index a7ebb8f..ae5d53d 100644 --- a/rss_parser/models/rss/image.py +++ b/rss_parser/models/rss/image.py @@ -14,7 +14,7 @@ class Image(XMLBaseModel): "Describes the image, it's used in the ALT attribute of the HTML <img> tag when the channel is rendered in HTML." link: Tag[str] - "The URL of the site, when the channel is rendered, the image is a link to the site. (Note, in practice the " "image <title> and <link> should have the same value as the channel's <title> and <link>." # noqa + "The URL of the site, when the channel is rendered, the image is a link to the site. (Note, in practice the image <title> and <link> should have the same value as the channel's <title> and <link>." # noqa width: Optional[Tag[int]] = None "Number, indicating the width of the image in pixels." diff --git a/rss_parser/models/rss/item.py b/rss_parser/models/rss/item.py index 3bbf144..1440770 100644 --- a/rss_parser/models/rss/item.py +++ b/rss_parser/models/rss/item.py @@ -1,18 +1,17 @@ from typing import Optional +from pydantic import Field + from rss_parser.models import XMLBaseModel from rss_parser.models.types.only_list import OnlyList from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class RequiredItemElementsMixin(XMLBaseModel): title: Tag[str] # Venice Film Festival Tries to Quit Sinking "The title of the item." - links: OnlyList[Tag[str]] = pydantic.Field(alias="link") # http://nytimes.com/2004/12/07FEST.html + links: OnlyList[Tag[str]] = Field(alias="link") # http://nytimes.com/2004/12/07FEST.html "The URL of the item." description: Tag[str] # <description>Some of the most heated chatter at the Venice Film Festival this week was @@ -24,15 +23,15 @@ class OptionalItemElementsMixin(XMLBaseModel): author: Optional[Tag[str]] = None "Email address of the author of the item." - categories: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="category", default=[]) + categories: Optional[OnlyList[Tag[str]]] = Field(alias="category", default_factory=list) "Includes the item in one or more categories." comments: Optional[Tag[str]] = None "URL of a page for comments relating to the item." - enclosures: Optional[OnlyList[Tag[str]]] = pydantic.Field(alias="enclosure", default=[]) + enclosures: Optional[OnlyList[Tag[str]]] = Field(alias="enclosure", default_factory=list) # enclosure: Optional[OnlyList[Tag[str]]] = None - "Describes a media object that is attached to the item.\n" "Can be a list -> https://validator.w3.org/feed/docs/warning/DuplicateEnclosure.html" + "Describes a media object that is attached to the item.\nCan be a list -> https://validator.w3.org/feed/docs/warning/DuplicateEnclosure.html" guid: Optional[Tag[str]] = None "A string that uniquely identifies the item." diff --git a/rss_parser/models/rss/rss.py b/rss_parser/models/rss/rss.py index 89d5bda..13fc9be 100644 --- a/rss_parser/models/rss/rss.py +++ b/rss_parser/models/rss/rss.py @@ -1,15 +1,14 @@ from typing import Optional +from pydantic import Field + from rss_parser.models import XMLBaseModel from rss_parser.models.rss.channel import Channel from rss_parser.models.types.tag import Tag -from rss_parser.pydantic_proxy import import_v1_pydantic - -pydantic = import_v1_pydantic() class RSS(XMLBaseModel): """RSS 2.0.""" - version: Optional[Tag[str]] = pydantic.Field(alias="@version") + version: Optional[Tag[str]] = Field(alias="@version", default=None) channel: Tag[Channel] diff --git a/rss_parser/models/types/__init__.py b/rss_parser/models/types/__init__.py index e69de29..c458080 100644 --- a/rss_parser/models/types/__init__.py +++ b/rss_parser/models/types/__init__.py @@ -0,0 +1,5 @@ +from rss_parser.models.types.date import DateTimeOrStr +from rss_parser.models.types.only_list import OnlyList +from rss_parser.models.types.tag import Tag + +__all__ = ("DateTimeOrStr", "OnlyList", "Tag") diff --git a/rss_parser/models/types/date.py b/rss_parser/models/types/date.py index 9b96e38..84c2db0 100644 --- a/rss_parser/models/types/date.py +++ b/rss_parser/models/types/date.py @@ -1,16 +1,19 @@ +from __future__ import annotations + from datetime import datetime from email.utils import parsedate_to_datetime from typing import Union -from rss_parser.pydantic_proxy import import_v1_pydantic +from pydantic import GetCoreSchemaHandler, TypeAdapter, ValidationError +from pydantic_core import core_schema -pydantic_validators = import_v1_pydantic(".validators") +datetime_adapter = TypeAdapter(datetime) class DateTimeOrStr(datetime): @classmethod - def __get_validators__(cls): - yield validate_dt_or_str + def __get_pydantic_core_schema__(cls, _source_type, _handler: GetCoreSchemaHandler): + return core_schema.no_info_plain_validator_function(cls.validate) @classmethod def __get_pydantic_json_schema__(cls, field_schema): @@ -19,14 +22,16 @@ def __get_pydantic_json_schema__(cls, field_schema): ) @classmethod - def validate(cls, v): - return validate_dt_or_str(v) + def validate(cls, value): + return validate_dt_or_str(value) def __repr__(self): - return f"DateTimeOrStp({super().__repr__()})" + return f"DateTimeOrStr({super().__repr__()})" -def validate_dt_or_str(value: str) -> Union[datetime, str]: +def validate_dt_or_str(value: Union[str, datetime], _info=None): + if isinstance(value, datetime): + return value # Try to parse standard (RFC 822) try: return parsedate_to_datetime(value) @@ -34,8 +39,8 @@ def validate_dt_or_str(value: str) -> Union[datetime, str]: pass # Try ISO or timestamp try: - return pydantic_validators.parse_datetime(value) - except ValueError: + return datetime_adapter.validate_python(value) + except ValidationError: pass return value diff --git a/rss_parser/models/types/only_list.py b/rss_parser/models/types/only_list.py index 80a8850..b920037 100644 --- a/rss_parser/models/types/only_list.py +++ b/rss_parser/models/types/only_list.py @@ -1,23 +1,33 @@ -from typing import Generic, TypeVar, Union +from __future__ import annotations -from rss_parser.pydantic_proxy import import_v1_pydantic +from typing import Annotated, Generic, TypeVar -pydantic_validators = import_v1_pydantic(".validators") +from pydantic.functional_validators import AfterValidator, BeforeValidator T = TypeVar("T") class OnlyList(list, Generic[T]): - @classmethod - def __get_validators__(cls): - yield cls.validate - yield pydantic_validators.list_validator + @staticmethod + def _ensure_list(value): + if isinstance(value, list): + return value + if value is None: + return [] + return [value] @classmethod - def validate(cls, v: Union[dict, list]): - if isinstance(v, list): - return cls(v) - return cls([v]) + def _as_only_list(cls, value): + if isinstance(value, cls): + return value + return cls(value) def __repr__(self): return f"OnlyList({super().__repr__()})" + + def __class_getitem__(cls, item): + return Annotated[ + list[item], + BeforeValidator(cls._ensure_list), + AfterValidator(cls._as_only_list), + ] diff --git a/rss_parser/models/types/tag.py b/rss_parser/models/types/tag.py index 3c6ec33..efd66b8 100644 --- a/rss_parser/models/types/tag.py +++ b/rss_parser/models/types/tag.py @@ -1,23 +1,18 @@ +from __future__ import annotations + from copy import deepcopy -from json import loads -from typing import TYPE_CHECKING, Generic, Optional, TypeVar, Union +from typing import Any, Dict, Generic, Optional, TypeVar, Union + +from pydantic import BaseModel, Field, model_validator +from pydantic.json import pydantic_encoder from rss_parser.models import XMLBaseModel from rss_parser.models.utils import snake_case -from rss_parser.pydantic_proxy import import_v1_pydantic - -if TYPE_CHECKING: - from pydantic.v1 import generics as pydantic_generics - from pydantic.v1 import json as pydantic_json -else: - pydantic = import_v1_pydantic() - pydantic_generics = import_v1_pydantic(".generics") - pydantic_json = import_v1_pydantic(".json") T = TypeVar("T") -class Tag(pydantic_generics.GenericModel, Generic[T]): +class Tag(BaseModel, Generic[T]): """ >>> from rss_parser.models import XMLBaseModel >>> from rss_parser.models.types.tag import Tag @@ -50,8 +45,8 @@ class Tag(pydantic_generics.GenericModel, Generic[T]): """ # Optional in case of self-closing tags - content: Optional[T] - attributes: dict + content: Optional[T] = None + attributes: Dict[str, Any] = Field(default_factory=dict) def __getattr__(self, item): """Forward default getattr for content for simplicity.""" @@ -63,29 +58,27 @@ def __getitem__(self, key): def __setitem__(self, key, value): self.content[key] = value + @model_validator(mode="before") @classmethod - def __get_validators__(cls): - yield cls.pre_convert - yield cls.validate - - @classmethod - def pre_convert(cls, v: Union[T, dict], **kwargs): # noqa + def pre_convert(cls, value: Union[T, dict, "Tag[T]"]) -> Union["Tag[T]", Dict[str, Any]]: """Used to split tag's text with other xml attributes.""" - if isinstance(v, dict): - data = deepcopy(v) + if isinstance(value, cls): + return value + + if isinstance(value, dict): + data = deepcopy(value) attributes = {snake_case(k.lstrip("@")): v for k, v in data.items() if k.startswith("@")} - content = data.pop("#text", data) if not len(attributes) == len(data) else None + content = data.pop("#text", data) if len(attributes) != len(data) else None return {"content": content, "attributes": attributes} - return {"content": v, "attributes": {}} + + return {"content": value, "attributes": {}} @classmethod - def flatten_tag_encoder(cls, v): + def flatten_tag_encoder(cls, value): """Encoder that translates Tag objects (dict) to plain .content values (T).""" - bases = v.__class__.__bases__ - if XMLBaseModel in bases: - # Can't pass encoder to .dict :/ - return loads(v.json_plain()) - if cls in bases: - return v.content + if isinstance(value, XMLBaseModel): + return value.dict_plain() + if isinstance(value, Tag): + return value.content - return pydantic_json.pydantic_encoder(v) + return pydantic_encoder(value) diff --git a/rss_parser/pydantic_proxy.py b/rss_parser/pydantic_proxy.py index 5b5ae8a..57c08ee 100644 --- a/rss_parser/pydantic_proxy.py +++ b/rss_parser/pydantic_proxy.py @@ -1,11 +1,3 @@ -from importlib import import_module -from importlib.metadata import version +from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic -_pydantic_version = version("pydantic") - - -def import_v1_pydantic(relative_submodule_path: str = ""): - if _pydantic_version[0] == "2": - return import_module("pydantic.v1" + relative_submodule_path) - else: - return import_module("pydantic" + relative_submodule_path) +__all__ = ("import_v1_pydantic",) diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 8d7ea96..11fa56f 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,11 +1,18 @@ -import logging from typing import Type import pytest from rss_parser import AtomParser, BaseParser, RSSParser +from rss_parser.models.legacy.atom import Atom as LegacyAtom +from rss_parser.models.legacy.rss import RSS as LegacyRSS -logger = logging.getLogger(__name__) + +class LegacyRSSParser(RSSParser): + schema = LegacyRSS + + +class LegacyAtomParser(AtomParser): + schema = LegacyAtom class DataHelper: @@ -16,10 +23,26 @@ def compare_parsing(sample_and_result, parser: Type[BaseParser]): assert rss - parsed = rss.dict() + if hasattr(rss, "model_dump"): + parsed = rss.model_dump() + else: + parsed = rss.dict() assert parsed == result +RSS_PARSERS = pytest.mark.parametrize( + "parser_cls", + [RSSParser, LegacyRSSParser], + ids=["rss-v2", "rss-legacy"], +) + +ATOM_PARSERS = pytest.mark.parametrize( + "parser_cls", + [AtomParser, LegacyAtomParser], + ids=["atom-v2", "atom-legacy"], +) + + @pytest.mark.usefixtures("sample_and_result") class TestRSS: @pytest.mark.parametrize( @@ -33,8 +56,9 @@ class TestRSS: ], indirect=True, ) - def test_parses_all_rss_samples(self, sample_and_result): - DataHelper.compare_parsing(sample_and_result, parser=RSSParser) + @RSS_PARSERS + def test_parses_all_rss_samples(self, sample_and_result, parser_cls): + DataHelper.compare_parsing(sample_and_result, parser=parser_cls) @pytest.mark.usefixtures("sample_and_result") @@ -47,5 +71,6 @@ class TestAtom: ], indirect=True, ) - def test_parses_all_atom_samples(self, sample_and_result): - DataHelper.compare_parsing(sample_and_result, parser=AtomParser) + @ATOM_PARSERS + def test_parses_all_atom_samples(self, sample_and_result, parser_cls): + DataHelper.compare_parsing(sample_and_result, parser=parser_cls) From 0f9c85040f584c24f3be29ec3fc8896c5a1518b6 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Sun, 16 Nov 2025 16:38:34 +0100 Subject: [PATCH 02/10] Fix mypy issues with OnlyList --- rss_parser/models/atom/entry.py | 8 ++++---- rss_parser/models/atom/feed.py | 10 +++++----- rss_parser/models/rss/channel.py | 4 ++-- rss_parser/models/rss/item.py | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/rss_parser/models/atom/entry.py b/rss_parser/models/atom/entry.py index 9c567a3..c0e9f20 100644 --- a/rss_parser/models/atom/entry.py +++ b/rss_parser/models/atom/entry.py @@ -21,10 +21,10 @@ class RequiredAtomEntryMixin(XMLBaseModel): class RecommendedAtomEntryMixin(XMLBaseModel): - authors: Optional[OnlyList[Tag[Person]]] = Field(alias="author", default_factory=list) + authors: OnlyList[Tag[Person]] = Field(alias="author", default_factory=OnlyList) "Entry authors." - links: Optional[OnlyList[Tag[str]]] = Field(alias="link", default_factory=list) + links: OnlyList[Tag[str]] = Field(alias="link", default_factory=OnlyList) "The URL of the entry." content: Optional[Tag[str]] = None @@ -35,10 +35,10 @@ class RecommendedAtomEntryMixin(XMLBaseModel): class OptionalAtomEntryMixin(XMLBaseModel): - categories: Optional[OnlyList[Tag[dict]]] = Field(alias="category", default_factory=list) + categories: OnlyList[Tag[dict]] = Field(alias="category", default_factory=OnlyList) "Specifies a categories that the entry belongs to." - contributors: Optional[OnlyList[Tag[Person]]] = Field(alias="contributor", default_factory=list) + contributors: OnlyList[Tag[Person]] = Field(alias="contributor", default_factory=OnlyList) "Entry contributors." rights: Optional[Tag[str]] = None diff --git a/rss_parser/models/atom/feed.py b/rss_parser/models/atom/feed.py index 8b5a3c7..3b0eb58 100644 --- a/rss_parser/models/atom/feed.py +++ b/rss_parser/models/atom/feed.py @@ -22,21 +22,21 @@ class RequiredAtomFeedMixin(XMLBaseModel): class RecommendedAtomFeedMixin(XMLBaseModel): - authors: Optional[OnlyList[Tag[Person]]] = Field(alias="author", default_factory=list) + authors: OnlyList[Tag[Person]] = Field(alias="author", default_factory=OnlyList) "Names one author of the feed. A feed may have multiple author elements." - links: Optional[OnlyList[Tag[str]]] = Field(alias="link", default_factory=list) + links: OnlyList[Tag[str]] = Field(alias="link", default_factory=OnlyList) "The URL to the feed. A feed may have multiple link elements." class OptionalAtomFeedMixin(XMLBaseModel): - entries: Optional[OnlyList[Tag[Entry]]] = Field(alias="entry", default_factory=list) + entries: OnlyList[Tag[Entry]] = Field(alias="entry", default_factory=OnlyList) "The entries in the feed. A feed may have multiple entry elements." - categories: Optional[OnlyList[Tag[dict]]] = Field(alias="category", default_factory=list) + categories: OnlyList[Tag[dict]] = Field(alias="category", default_factory=OnlyList) "Specifies a categories that the feed belongs to. The feed may have multiple categories elements." - contributors: Optional[OnlyList[Tag[Person]]] = Field(alias="contributor", default_factory=list) + contributors: OnlyList[Tag[Person]] = Field(alias="contributor", default_factory=OnlyList) "Feed contributors." generator: Optional[Tag[str]] = None diff --git a/rss_parser/models/rss/channel.py b/rss_parser/models/rss/channel.py index c9bfeb0..6bbfbb0 100644 --- a/rss_parser/models/rss/channel.py +++ b/rss_parser/models/rss/channel.py @@ -27,7 +27,7 @@ class RequiredChannelElementsMixin(XMLBaseModel): class OptionalChannelElementsMixin(XMLBaseModel): """https://www.rssboard.org/rss-specification#optionalChannelElements.""" - items: Optional[OnlyList[Tag[Item]]] = Field(alias="item", default_factory=list) + items: OnlyList[Tag[Item]] = Field(alias="item", default_factory=OnlyList) language: Optional[Tag[str]] = None # en-us "The language the channel is written in. This allows aggregators to group all Italian language sites, for example, on a single page." # noqa @@ -46,7 +46,7 @@ class OptionalChannelElementsMixin(XMLBaseModel): last_build_date: Optional[Tag[DateTimeOrStr]] = None # Sat, 07 Sep 2002 09:42:31 GMT "The last time the content of the channel changed." - categories: Optional[OnlyList[Tag[str]]] = Field(alias="category", default_factory=list) + categories: OnlyList[Tag[str]] = Field(alias="category", default_factory=OnlyList) "Specify one or more categories that the channel belongs to. Follows the same rules as the <item.py>-level category element." # noqa generator: Optional[Tag[str]] = None # MightyInHouse Content System v2.3 diff --git a/rss_parser/models/rss/item.py b/rss_parser/models/rss/item.py index 1440770..771a0ac 100644 --- a/rss_parser/models/rss/item.py +++ b/rss_parser/models/rss/item.py @@ -23,13 +23,13 @@ class OptionalItemElementsMixin(XMLBaseModel): author: Optional[Tag[str]] = None "Email address of the author of the item." - categories: Optional[OnlyList[Tag[str]]] = Field(alias="category", default_factory=list) + categories: OnlyList[Tag[str]] = Field(alias="category", default_factory=OnlyList) "Includes the item in one or more categories." comments: Optional[Tag[str]] = None "URL of a page for comments relating to the item." - enclosures: Optional[OnlyList[Tag[str]]] = Field(alias="enclosure", default_factory=list) + enclosures: OnlyList[Tag[str]] = Field(alias="enclosure", default_factory=OnlyList) # enclosure: Optional[OnlyList[Tag[str]]] = None "Describes a media object that is attached to the item.\nCan be a list -> https://validator.w3.org/feed/docs/warning/DuplicateEnclosure.html" From 11c6e08714985ef4bc7b16f2a0d6607ae58f327a Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Sun, 16 Nov 2025 16:45:44 +0100 Subject: [PATCH 03/10] Re-lock, update classifiers --- poetry.lock | 720 +++++++++++++++++++++++++++---------------------- pyproject.toml | 2 + 2 files changed, 400 insertions(+), 322 deletions(-) diff --git a/poetry.lock b/poetry.lock index 5782955..67d58c4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,48 +13,52 @@ files = [ [[package]] name = "asttokens" -version = "3.0.0" +version = "3.0.1" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" files = [ - {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, - {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, + {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, + {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, ] [package.extras] -astroid = ["astroid (>=2,<4)"] -test = ["astroid (>=2,<4)", "pytest", "pytest-cov", "pytest-xdist"] +astroid = ["astroid (>=2,<5)"] +test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] [[package]] name = "black" -version = "25.1.0" +version = "25.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" files = [ - {file = "black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32"}, - {file = "black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da"}, - {file = "black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7"}, - {file = "black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9"}, - {file = "black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0"}, - {file = "black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299"}, - {file = "black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096"}, - {file = "black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2"}, - {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"}, - {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"}, - {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"}, - {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"}, - {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"}, - {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"}, - {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"}, - {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"}, - {file = "black-25.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0"}, - {file = "black-25.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f"}, - {file = "black-25.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e"}, - {file = "black-25.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355"}, - {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"}, - {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"}, + {file = "black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e"}, + {file = "black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0"}, + {file = "black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37"}, + {file = "black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03"}, + {file = "black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a"}, + {file = "black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170"}, + {file = "black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc"}, + {file = "black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e"}, + {file = "black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac"}, + {file = "black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96"}, + {file = "black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd"}, + {file = "black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409"}, + {file = "black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b"}, + {file = "black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd"}, + {file = "black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993"}, + {file = "black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c"}, + {file = "black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170"}, + {file = "black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545"}, + {file = "black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda"}, + {file = "black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664"}, + {file = "black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06"}, + {file = "black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2"}, + {file = "black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc"}, + {file = "black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc"}, + {file = "black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b"}, + {file = "black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08"}, ] [package.dependencies] @@ -63,6 +67,7 @@ mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" +pytokens = ">=0.3.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} @@ -121,13 +126,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] @@ -149,13 +154,13 @@ test = ["pytest (>=6)"] [[package]] name = "executing" -version = "2.2.0" +version = "2.2.1" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" files = [ - {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, - {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, + {file = "executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017"}, + {file = "executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4"}, ] [package.extras] @@ -163,29 +168,24 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "filelock" -version = "3.18.0" +version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" files = [ - {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, - {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, + {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, + {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] -[package.extras] -docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] -typing = ["typing-extensions (>=4.12.2)"] - [[package]] name = "identify" -version = "2.6.12" +version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" files = [ - {file = "identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2"}, - {file = "identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6"}, + {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, + {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, ] [package.extras] @@ -284,18 +284,21 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "matplotlib-inline" -version = "0.1.7" +version = "0.2.1" description = "Inline Matplotlib backend for Jupyter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, - {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, + {file = "matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76"}, + {file = "matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe"}, ] [package.dependencies] traitlets = "*" +[package.extras] +test = ["flake8", "nbdime", "nbval", "notebook", "pytest"] + [[package]] name = "mdurl" version = "0.1.2" @@ -309,43 +312,49 @@ files = [ [[package]] name = "mypy" -version = "1.16.1" +version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" files = [ - {file = "mypy-1.16.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4f0fed1022a63c6fec38f28b7fc77fca47fd490445c69d0a66266c59dd0b88a"}, - {file = "mypy-1.16.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86042bbf9f5a05ea000d3203cf87aa9d0ccf9a01f73f71c58979eb9249f46d72"}, - {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea7469ee5902c95542bea7ee545f7006508c65c8c54b06dc2c92676ce526f3ea"}, - {file = "mypy-1.16.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:352025753ef6a83cb9e7f2427319bb7875d1fdda8439d1e23de12ab164179574"}, - {file = "mypy-1.16.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ff9fa5b16e4c1364eb89a4d16bcda9987f05d39604e1e6c35378a2987c1aac2d"}, - {file = "mypy-1.16.1-cp310-cp310-win_amd64.whl", hash = "sha256:1256688e284632382f8f3b9e2123df7d279f603c561f099758e66dd6ed4e8bd6"}, - {file = "mypy-1.16.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:472e4e4c100062488ec643f6162dd0d5208e33e2f34544e1fc931372e806c0cc"}, - {file = "mypy-1.16.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea16e2a7d2714277e349e24d19a782a663a34ed60864006e8585db08f8ad1782"}, - {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08e850ea22adc4d8a4014651575567b0318ede51e8e9fe7a68f25391af699507"}, - {file = "mypy-1.16.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22d76a63a42619bfb90122889b903519149879ddbf2ba4251834727944c8baca"}, - {file = "mypy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c7ce0662b6b9dc8f4ed86eb7a5d505ee3298c04b40ec13b30e572c0e5ae17c4"}, - {file = "mypy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:211287e98e05352a2e1d4e8759c5490925a7c784ddc84207f4714822f8cf99b6"}, - {file = "mypy-1.16.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:af4792433f09575d9eeca5c63d7d90ca4aeceda9d8355e136f80f8967639183d"}, - {file = "mypy-1.16.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66df38405fd8466ce3517eda1f6640611a0b8e70895e2a9462d1d4323c5eb4b9"}, - {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44e7acddb3c48bd2713994d098729494117803616e116032af192871aed80b79"}, - {file = "mypy-1.16.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ab5eca37b50188163fa7c1b73c685ac66c4e9bdee4a85c9adac0e91d8895e15"}, - {file = "mypy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb6229b2c9086247e21a83c309754b9058b438704ad2f6807f0d8227f6ebdd"}, - {file = "mypy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:1f0435cf920e287ff68af3d10a118a73f212deb2ce087619eb4e648116d1fe9b"}, - {file = "mypy-1.16.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ddc91eb318c8751c69ddb200a5937f1232ee8efb4e64e9f4bc475a33719de438"}, - {file = "mypy-1.16.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:87ff2c13d58bdc4bbe7dc0dedfe622c0f04e2cb2a492269f3b418df2de05c536"}, - {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a7cfb0fe29fe5a9841b7c8ee6dffb52382c45acdf68f032145b75620acfbd6f"}, - {file = "mypy-1.16.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:051e1677689c9d9578b9c7f4d206d763f9bbd95723cd1416fad50db49d52f359"}, - {file = "mypy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d5d2309511cc56c021b4b4e462907c2b12f669b2dbeb68300110ec27723971be"}, - {file = "mypy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:4f58ac32771341e38a853c5d0ec0dfe27e18e27da9cdb8bbc882d2249c71a3ee"}, - {file = "mypy-1.16.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7fc688329af6a287567f45cc1cefb9db662defeb14625213a5b7da6e692e2069"}, - {file = "mypy-1.16.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e198ab3f55924c03ead626ff424cad1732d0d391478dfbf7bb97b34602395da"}, - {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09aa4f91ada245f0a45dbc47e548fd94e0dd5a8433e0114917dc3b526912a30c"}, - {file = "mypy-1.16.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13c7cd5b1cb2909aa318a90fd1b7e31f17c50b242953e7dd58345b2a814f6383"}, - {file = "mypy-1.16.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:58e07fb958bc5d752a280da0e890c538f1515b79a65757bbdc54252ba82e0b40"}, - {file = "mypy-1.16.1-cp39-cp39-win_amd64.whl", hash = "sha256:f895078594d918f93337a505f8add9bd654d1a24962b4c6ed9390e12531eb31b"}, - {file = "mypy-1.16.1-py3-none-any.whl", hash = "sha256:5fc2ac4027d0ef28d6ba69a0343737a23c4d1b83672bf38d1fe237bdc0643b37"}, - {file = "mypy-1.16.1.tar.gz", hash = "sha256:6bd00a0a2094841c5e47e7374bb42b83d64c527a502e3334e1173a0c24437bab"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"}, + {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"}, + {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"}, + {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"}, + {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"}, + {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"}, + {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"}, + {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"}, + {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"}, + {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"}, + {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"}, + {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"}, + {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"}, + {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"}, + {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"}, + {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"}, + {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"}, + {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"}, + {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"}, + {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"}, + {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"}, + {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"}, + {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"}, + {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"}, + {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"}, + {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"}, + {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"}, ] [package.dependencies] @@ -396,13 +405,13 @@ files = [ [[package]] name = "parso" -version = "0.8.4" +version = "0.8.5" description = "A Python Parser" optional = false python-versions = ">=3.6" files = [ - {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, - {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, + {file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"}, + {file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"}, ] [package.extras] @@ -436,13 +445,13 @@ ptyprocess = ">=0.5" [[package]] name = "platformdirs" -version = "4.3.8" +version = "4.4.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" files = [ - {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, - {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, + {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, + {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, ] [package.extras] @@ -485,13 +494,13 @@ virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" -version = "3.0.51" +version = "3.0.52" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" files = [ - {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, - {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, + {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, + {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, ] [package.dependencies] @@ -524,20 +533,20 @@ tests = ["pytest"] [[package]] name = "pydantic" -version = "2.11.7" +version = "2.12.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, - {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, + {file = "pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}, + {file = "pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.33.2" -typing-extensions = ">=4.12.2" -typing-inspection = ">=0.4.0" +pydantic-core = "2.41.5" +typing-extensions = ">=4.14.1" +typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -545,114 +554,136 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.33.2" +version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" files = [ - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, - {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, - {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, - {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, - {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, - {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, - {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, - {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, - {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, - {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, - {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, - {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, - {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, - {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, - {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, - {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, - {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, - {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, - {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, - {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, - {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, - {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, - {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, - {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, ] [package.dependencies] -typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +typing-extensions = ">=4.14.1" [[package]] name = "pygments" @@ -690,112 +721,146 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +[[package]] +name = "pytokens" +version = "0.3.0" +description = "A Fast, spec compliant Python 3.14+ tokenizer that runs on older Pythons." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"}, + {file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"}, +] + +[package.extras] +dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"] + [[package]] name = "pyyaml" -version = "6.0.2" +version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" files = [ - {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, - {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, - {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, - {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, - {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, - {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, - {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, - {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, - {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, - {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, - {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, - {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, - {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, - {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, - {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, - {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, - {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, - {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, - {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, - {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, - {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, - {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, - {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, - {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, - {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, - {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, - {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, - {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, - {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, - {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, - {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, - {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, + {file = "PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3"}, + {file = "PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6"}, + {file = "PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369"}, + {file = "PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295"}, + {file = "PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b"}, + {file = "pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198"}, + {file = "pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0"}, + {file = "pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69"}, + {file = "pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e"}, + {file = "pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e"}, + {file = "pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00"}, + {file = "pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a"}, + {file = "pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4"}, + {file = "pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b"}, + {file = "pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196"}, + {file = "pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c"}, + {file = "pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e"}, + {file = "pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea"}, + {file = "pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b"}, + {file = "pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8"}, + {file = "pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5"}, + {file = "pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6"}, + {file = "pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be"}, + {file = "pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c"}, + {file = "pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac"}, + {file = "pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788"}, + {file = "pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764"}, + {file = "pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac"}, + {file = "pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3"}, + {file = "pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702"}, + {file = "pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065"}, + {file = "pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9"}, + {file = "pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da"}, + {file = "pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5"}, + {file = "pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926"}, + {file = "pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7"}, + {file = "pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0"}, + {file = "pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007"}, + {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] [[package]] name = "rich" -version = "14.0.0" +version = "14.2.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" files = [ - {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, - {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, + {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, + {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, ] [package.dependencies] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "ruff" -version = "0.12.2" +version = "0.14.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.12.2-py3-none-linux_armv6l.whl", hash = "sha256:093ea2b221df1d2b8e7ad92fc6ffdca40a2cb10d8564477a987b44fd4008a7be"}, - {file = "ruff-0.12.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:09e4cf27cc10f96b1708100fa851e0daf21767e9709e1649175355280e0d950e"}, - {file = "ruff-0.12.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8ae64755b22f4ff85e9c52d1f82644abd0b6b6b6deedceb74bd71f35c24044cc"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eb3a6b2db4d6e2c77e682f0b988d4d61aff06860158fdb413118ca133d57922"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:73448de992d05517170fc37169cbca857dfeaeaa8c2b9be494d7bcb0d36c8f4b"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b94317cbc2ae4a2771af641739f933934b03555e51515e6e021c64441532d"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45fc42c3bf1d30d2008023a0a9a0cfb06bf9835b147f11fe0679f21ae86d34b1"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce48f675c394c37e958bf229fb5c1e843e20945a6d962cf3ea20b7a107dcd9f4"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:793d8859445ea47591272021a81391350205a4af65a9392401f418a95dfb75c9"}, - {file = "ruff-0.12.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6932323db80484dda89153da3d8e58164d01d6da86857c79f1961934354992da"}, - {file = "ruff-0.12.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6aa7e623a3a11538108f61e859ebf016c4f14a7e6e4eba1980190cacb57714ce"}, - {file = "ruff-0.12.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2a4a20aeed74671b2def096bdf2eac610c7d8ffcbf4fb0e627c06947a1d7078d"}, - {file = "ruff-0.12.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:71a4c550195612f486c9d1f2b045a600aeba851b298c667807ae933478fcef04"}, - {file = "ruff-0.12.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4987b8f4ceadf597c927beee65a5eaf994c6e2b631df963f86d8ad1bdea99342"}, - {file = "ruff-0.12.2-py3-none-win32.whl", hash = "sha256:369ffb69b70cd55b6c3fc453b9492d98aed98062db9fec828cdfd069555f5f1a"}, - {file = "ruff-0.12.2-py3-none-win_amd64.whl", hash = "sha256:dca8a3b6d6dc9810ed8f328d406516bf4d660c00caeaef36eb831cf4871b0639"}, - {file = "ruff-0.12.2-py3-none-win_arm64.whl", hash = "sha256:48d6c6bfb4761df68bc05ae630e24f506755e702d4fb08f08460be778c7ccb12"}, - {file = "ruff-0.12.2.tar.gz", hash = "sha256:d7b4f55cd6f325cb7621244f19c873c565a08aff5a4ba9c69aa7355f3f7afd3e"}, + {file = "ruff-0.14.5-py3-none-linux_armv6l.whl", hash = "sha256:f3b8248123b586de44a8018bcc9fefe31d23dda57a34e6f0e1e53bd51fd63594"}, + {file = "ruff-0.14.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f7a75236570318c7a30edd7f5491945f0169de738d945ca8784500b517163a72"}, + {file = "ruff-0.14.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6d146132d1ee115f8802356a2dc9a634dbf58184c51bff21f313e8cd1c74899a"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2380596653dcd20b057794d55681571a257a42327da8894b93bbd6111aa801f"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2d1fa985a42b1f075a098fa1ab9d472b712bdb17ad87a8ec86e45e7fa6273e68"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88f0770d42b7fa02bbefddde15d235ca3aa24e2f0137388cc15b2dcbb1f7c7a7"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3676cb02b9061fee7294661071c4709fa21419ea9176087cb77e64410926eb78"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b595bedf6bc9cab647c4a173a61acf4f1ac5f2b545203ba82f30fcb10b0318fb"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f55382725ad0bdb2e8ee2babcbbfb16f124f5a59496a2f6a46f1d9d99d93e6e2"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7497d19dce23976bdaca24345ae131a1d38dcfe1b0850ad8e9e6e4fa321a6e19"}, + {file = "ruff-0.14.5-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:410e781f1122d6be4f446981dd479470af86537fb0b8857f27a6e872f65a38e4"}, + {file = "ruff-0.14.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01be527ef4c91a6d55e53b337bfe2c0f82af024cc1a33c44792d6844e2331e1"}, + {file = "ruff-0.14.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f66e9bb762e68d66e48550b59c74314168ebb46199886c5c5aa0b0fbcc81b151"}, + {file = "ruff-0.14.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d93be8f1fa01022337f1f8f3bcaa7ffee2d0b03f00922c45c2207954f351f465"}, + {file = "ruff-0.14.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c135d4b681f7401fe0e7312017e41aba9b3160861105726b76cfa14bc25aa367"}, + {file = "ruff-0.14.5-py3-none-win32.whl", hash = "sha256:c83642e6fccfb6dea8b785eb9f456800dcd6a63f362238af5fc0c83d027dd08b"}, + {file = "ruff-0.14.5-py3-none-win_amd64.whl", hash = "sha256:9d55d7af7166f143c94eae1db3312f9ea8f95a4defef1979ed516dbb38c27621"}, + {file = "ruff-0.14.5-py3-none-win_arm64.whl", hash = "sha256:4b700459d4649e2594b31f20a9de33bc7c19976d4746d8d0798ad959621d64a4"}, + {file = "ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1"}, ] [[package]] @@ -819,43 +884,53 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "tomli" -version = "2.2.1" +version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ - {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, - {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"}, - {file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"}, - {file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"}, - {file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"}, - {file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"}, - {file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"}, - {file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"}, - {file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"}, - {file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"}, - {file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"}, - {file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"}, - {file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"}, - {file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"}, - {file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"}, - {file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"}, - {file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"}, - {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, + {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, + {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, + {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, + {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, + {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, + {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, + {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, + {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, + {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, + {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, + {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, + {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, + {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, + {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, + {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, + {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, + {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, + {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, + {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, + {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, + {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, + {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, + {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, + {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, + {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, + {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, + {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, ] [[package]] @@ -886,24 +961,24 @@ files = [ [[package]] name = "typing-extensions" -version = "4.14.1" +version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, - {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, + {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, + {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, ] [[package]] name = "typing-inspection" -version = "0.4.1" +version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" files = [ - {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, - {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, + {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, + {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, ] [package.dependencies] @@ -911,19 +986,20 @@ typing-extensions = ">=4.12.0" [[package]] name = "virtualenv" -version = "20.31.2" +version = "20.35.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, - {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, + {file = "virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b"}, + {file = "virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" +typing-extensions = {version = ">=4.13.2", markers = "python_version < \"3.11\""} [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] @@ -931,13 +1007,13 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess [[package]] name = "wcwidth" -version = "0.2.13" +version = "0.2.14" description = "Measures the displayed width of unicode strings in a terminal" optional = false -python-versions = "*" +python-versions = ">=3.6" files = [ - {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, - {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, + {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, + {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, ] [[package]] @@ -954,4 +1030,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "1b71745d7a43c0dfe2d374bcf87b4b3fbcb04c2c4116cc2348a33bfbc80d7c61" +content-hash = "fe37607bd29ea8b94e49ee801bc401f8f0b627967e08e01fca92fc437781e2f2" diff --git a/pyproject.toml b/pyproject.toml index f093c46..74e91f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] packages = [{ include = "rss_parser" }, { include = "rss_parser/py.typed" }] From dbcbc9a1372eaacc3ad07cd949a9fb9d5cc4a1e5 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Sun, 16 Nov 2025 16:48:43 +0100 Subject: [PATCH 04/10] Try fix pre commit mypy --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 74e91f7..4b28bca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,6 +100,8 @@ ignore-names = ["LegacyRSS"] "**/__init__.py" = ["F401"] "rss_parser/models/atom/**" = ["A003"] +[tool.mypy] +explicit_package_bases = true [build-system] requires = ["poetry-core"] From cdcca199cbc9fd342a336b304a683738d156c130 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Sun, 16 Nov 2025 17:44:07 +0100 Subject: [PATCH 05/10] Fix mypy pre-push hook --- .pre-commit-config.yaml | 1 + pyproject.toml | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 18d0037..a7a3749 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,6 +31,7 @@ repos: - run - mypy - rss_parser + pass_filenames: false language: system stages: [ push ] diff --git a/pyproject.toml b/pyproject.toml index 4b28bca..6d27ad8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,9 +100,6 @@ ignore-names = ["LegacyRSS"] "**/__init__.py" = ["F401"] "rss_parser/models/atom/**" = ["A003"] -[tool.mypy] -explicit_package_bases = true - [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" From d7091d0ef2c7e84c464769d27d5258ceb9bbe92d Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Mon, 17 Nov 2025 13:48:07 +0100 Subject: [PATCH 06/10] Add import guard for legacy models, fix tests for 3.14 --- rss_parser/models/legacy/__init__.py | 4 +++ tests/test_parsing.py | 38 ++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/rss_parser/models/legacy/__init__.py b/rss_parser/models/legacy/__init__.py index 09215e0..073675b 100644 --- a/rss_parser/models/legacy/__init__.py +++ b/rss_parser/models/legacy/__init__.py @@ -4,9 +4,13 @@ Some types and validation may be a bit custom to account for broken standards in some RSS feeds. """ +import sys from json import loads from typing import TYPE_CHECKING +if sys.version_info >= (3, 14): + raise ImportError("Legacy models are not supported in Python 3.14 and above") + from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic from rss_parser.models.legacy.utils import camel_case diff --git a/tests/test_parsing.py b/tests/test_parsing.py index 11fa56f..f8b9363 100644 --- a/tests/test_parsing.py +++ b/tests/test_parsing.py @@ -1,18 +1,29 @@ +import sys from typing import Type import pytest from rss_parser import AtomParser, BaseParser, RSSParser -from rss_parser.models.legacy.atom import Atom as LegacyAtom -from rss_parser.models.legacy.rss import RSS as LegacyRSS +if sys.version_info < (3, 14): + from rss_parser.models.legacy.atom import Atom as LegacyAtom + from rss_parser.models.legacy.rss import RSS as LegacyRSS -class LegacyRSSParser(RSSParser): - schema = LegacyRSS + class LegacyRSSParser(RSSParser): + schema = LegacyRSS + class LegacyAtomParser(AtomParser): + schema = LegacyAtom -class LegacyAtomParser(AtomParser): - schema = LegacyAtom + rss_parser_list = [RSSParser, LegacyRSSParser] + rss_ids = ["rss-v2", "rss-legacy"] + atom_parser_list = [AtomParser, LegacyAtomParser] + atom_ids = ["atom-v2", "atom-legacy"] +else: + rss_parser_list = [RSSParser] + rss_ids = ["rss-v2"] + atom_parser_list = [AtomParser] + atom_ids = ["atom-v2"] class DataHelper: @@ -32,14 +43,14 @@ def compare_parsing(sample_and_result, parser: Type[BaseParser]): RSS_PARSERS = pytest.mark.parametrize( "parser_cls", - [RSSParser, LegacyRSSParser], - ids=["rss-v2", "rss-legacy"], + rss_parser_list, + ids=rss_ids, ) ATOM_PARSERS = pytest.mark.parametrize( "parser_cls", - [AtomParser, LegacyAtomParser], - ids=["atom-v2", "atom-legacy"], + atom_parser_list, + ids=atom_ids, ) @@ -74,3 +85,10 @@ class TestAtom: @ATOM_PARSERS def test_parses_all_atom_samples(self, sample_and_result, parser_cls): DataHelper.compare_parsing(sample_and_result, parser=parser_cls) + + +class TestLegacyImportError: + @pytest.mark.skipif(sys.version_info < (3, 14), reason="Legacy models still work in Python 3.13 and below") + def test_legacy_import_error(self): + with pytest.raises(ImportError): + from rss_parser.models.legacy import XMLBaseModel # noqa: F401, PLC0415 From 58169e14b476bcf691d8f3f99ae856a3a061ffaf Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Mon, 17 Nov 2025 14:13:01 +0100 Subject: [PATCH 07/10] Update paths-ignore in ci.yml --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf3f425..6411690 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,8 @@ on: paths-ignore: - ".gitignore" - "README.md" + - "pre-commit-config.yaml" + - "LICENSE" pull_request: jobs: From 4fdbc75444d1780042870e3b223feb0dace8f866 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Mon, 17 Nov 2025 14:15:08 +0100 Subject: [PATCH 08/10] Bump version to alpha --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6d27ad8..be3d44c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rss-parser" -version = "2.1.1" +version = "3.0.0a1" description = "Typed pythonic RSS/Atom parser" authors = ["dhvcc <1337kwiz@gmail.com>"] license = "GPL-3.0" From 06c2b2157b4098a595632eaf2aa07c68280e3856 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Mon, 17 Nov 2025 14:17:58 +0100 Subject: [PATCH 09/10] Disable PR runs since they'll still happen on regular pushes --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6411690..e701b79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,6 @@ on: - "README.md" - "pre-commit-config.yaml" - "LICENSE" - pull_request: jobs: test: From 43efc4cdbc48add1b2bbc3cea18bdc596b8c45f4 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Mon, 17 Nov 2025 14:34:39 +0100 Subject: [PATCH 10/10] v3.0.0a2 Add v2-v3 migration guide --- README.md | 11 +++++++++++ pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3b212a0..ba47ce6 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,17 @@ pip install dist/*.whl - Models for RSS-specific schemas have been moved from `rss_parser.models` to `rss_parser.models.rss`. Generic types remain unchanged - Date parsing has been improved and now uses pydantic's `validator` instead of `email.utils`, producing better datetime objects where it previously defaulted to `str` +## V2 -> V3 Migration + +`rss-parser` 3.x upgrades the runtime models to [Pydantic v2](https://docs.pydantic.dev/latest/migration/). Highlights: + +- **New default models** now inherit from `pydantic.BaseModel` v2 and use `model_validate`/`model_dump`. If you extend our classes, switch from `dict()`/`json()` to `model_dump()`/`model_dump_json()`. +- **Legacy compatibility** lives under `rss_parser.models.legacy`. Point your custom parser at the legacy schema if you must stay on the v1 API surface. +- **Collections**: list-like XML fields now use `OnlyList[...]` directly with an automatic `default_factory` so that attributes are always lists (no more `Optional[OnlyList[T]] = Field(..., default=[])`). Update custom schemas accordingly. +- **Custom hooks**: if you relied on `rss_parser.pydantic_proxy`, import it from `rss_parser.models.legacy.pydantic_proxy`. The top-level module only re-exports it for backwards compatibility. + +See the “Legacy Models” section below for sample snippets showing how to stay on the older types. Tests in this repo cover both tracks to guarantee matching output. + ## Legacy Models Pydantic v1-based models are still available under `rss_parser.models.legacy`. They retain the previous behaviour and re-export the `import_v1_pydantic` helper as `rss_parser.models.legacy.pydantic_proxy.import_v1_pydantic`. You can continue to use them by pointing your parser at the legacy schema: diff --git a/pyproject.toml b/pyproject.toml index be3d44c..65d9ecc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rss-parser" -version = "3.0.0a1" +version = "3.0.0a2" description = "Typed pythonic RSS/Atom parser" authors = ["dhvcc <1337kwiz@gmail.com>"] license = "GPL-3.0"