From 305c9ec99b1fe7b6672f148c1c8f2eee9f039add Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 00:24:36 +0200 Subject: [PATCH 01/20] feat!: remove legacy pydantic v1 models Pydantic v1 compatibility models under rss_parser.models.legacy and the rss_parser.pydantic_proxy re-export are gone. They were already broken on Python 3.14. Users who need pydantic v1 should stay on rss-parser 3.x. --- rss_parser/models/legacy/__init__.py | 36 -------- 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/pydantic_proxy.py | 3 - 20 files changed, 589 deletions(-) delete mode 100644 rss_parser/models/legacy/__init__.py delete mode 100644 rss_parser/models/legacy/atom/__init__.py delete mode 100644 rss_parser/models/legacy/atom/atom.py delete mode 100644 rss_parser/models/legacy/atom/entry.py delete mode 100644 rss_parser/models/legacy/atom/feed.py delete mode 100644 rss_parser/models/legacy/atom/person.py delete mode 100644 rss_parser/models/legacy/atom/source.py delete mode 100644 rss_parser/models/legacy/pydantic_proxy.py delete mode 100644 rss_parser/models/legacy/rss/__init__.py delete mode 100644 rss_parser/models/legacy/rss/channel.py delete mode 100644 rss_parser/models/legacy/rss/image.py delete mode 100644 rss_parser/models/legacy/rss/item.py delete mode 100644 rss_parser/models/legacy/rss/rss.py delete mode 100644 rss_parser/models/legacy/rss/text_input.py delete mode 100644 rss_parser/models/legacy/types/__init__.py delete mode 100644 rss_parser/models/legacy/types/date.py delete mode 100644 rss_parser/models/legacy/types/only_list.py delete mode 100644 rss_parser/models/legacy/types/tag.py delete mode 100644 rss_parser/models/legacy/utils.py delete mode 100644 rss_parser/pydantic_proxy.py diff --git a/rss_parser/models/legacy/__init__.py b/rss_parser/models/legacy/__init__.py deleted file mode 100644 index 073675b..0000000 --- a/rss_parser/models/legacy/__init__.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -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. -""" - -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 - -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 deleted file mode 100644 index 187c4b1..0000000 --- a/rss_parser/models/legacy/atom/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .atom import Atom - -__all__ = ("Atom",) diff --git a/rss_parser/models/legacy/atom/atom.py b/rss_parser/models/legacy/atom/atom.py deleted file mode 100644 index 48a3240..0000000 --- a/rss_parser/models/legacy/atom/atom.py +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index ed73fb9..0000000 --- a/rss_parser/models/legacy/atom/entry.py +++ /dev/null @@ -1,56 +0,0 @@ -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 deleted file mode 100644 index 4f4af91..0000000 --- a/rss_parser/models/legacy/atom/feed.py +++ /dev/null @@ -1,61 +0,0 @@ -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 deleted file mode 100644 index 83b59cd..0000000 --- a/rss_parser/models/legacy/atom/person.py +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 0b00165..0000000 --- a/rss_parser/models/legacy/atom/source.py +++ /dev/null @@ -1,19 +0,0 @@ -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 deleted file mode 100644 index 5b5ae8a..0000000 --- a/rss_parser/models/legacy/pydantic_proxy.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index 67d9354..0000000 --- a/rss_parser/models/legacy/rss/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .rss import RSS - -__all__ = ("RSS",) diff --git a/rss_parser/models/legacy/rss/channel.py b/rss_parser/models/legacy/rss/channel.py deleted file mode 100644 index 1d35594..0000000 --- a/rss_parser/models/legacy/rss/channel.py +++ /dev/null @@ -1,82 +0,0 @@ -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 deleted file mode 100644 index 2b91436..0000000 --- a/rss_parser/models/legacy/rss/image.py +++ /dev/null @@ -1,26 +0,0 @@ -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 deleted file mode 100644 index 82e9f1b..0000000 --- a/rss_parser/models/legacy/rss/item.py +++ /dev/null @@ -1,48 +0,0 @@ -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 deleted file mode 100644 index 8bd13a4..0000000 --- a/rss_parser/models/legacy/rss/rss.py +++ /dev/null @@ -1,15 +0,0 @@ -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 deleted file mode 100644 index 37be47c..0000000 --- a/rss_parser/models/legacy/rss/text_input.py +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 0794ecc..0000000 --- a/rss_parser/models/legacy/types/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index edf6b13..0000000 --- a/rss_parser/models/legacy/types/date.py +++ /dev/null @@ -1,41 +0,0 @@ -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 deleted file mode 100644 index 21679b1..0000000 --- a/rss_parser/models/legacy/types/only_list.py +++ /dev/null @@ -1,23 +0,0 @@ -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 deleted file mode 100644 index 159ecc6..0000000 --- a/rss_parser/models/legacy/types/tag.py +++ /dev/null @@ -1,91 +0,0 @@ -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 deleted file mode 100644 index 5bef900..0000000 --- a/rss_parser/models/legacy/utils.py +++ /dev/null @@ -1,10 +0,0 @@ -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/pydantic_proxy.py b/rss_parser/pydantic_proxy.py deleted file mode 100644 index 57c08ee..0000000 --- a/rss_parser/pydantic_proxy.py +++ /dev/null @@ -1,3 +0,0 @@ -from rss_parser.models.legacy.pydantic_proxy import import_v1_pydantic - -__all__ = ("import_v1_pydantic",) From 9adf5594ab0779255728b46649802ead1a322476 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:24:57 +0200 Subject: [PATCH 02/20] feat!: spec-compliant, generic, lossless models RSS 2.0 spec fixes: - All <item> elements are now optional; a validator enforces the one spec rule (title or description must be present). Feeds with description-only items no longer fail validation - <skipHours>/<skipDays> are proper models with hour/day lists instead of Tag[str] that crashed on any feed using them - <textInput> is typed as the TextInput model and <rating> as a string (they were swapped) - Added the missing <managingEditor> field - item.pubDate parses to datetime, ttl to int Extensibility: - Channel/RSS/Feed/Atom are generic (PEP 696 defaults), so a custom item schema plugs in with one parametrization: RSS[Channel[MyItem]] - Unknown tags are kept in model_extra (extra='allow') instead of being silently dropped - Models accept population by field name as well as alias - atom Entry.source is typed as Source instead of str - Required/Optional mixin classes are flattened into their models --- rss_parser/models/__init__.py | 16 +++++++++++- rss_parser/models/atom/atom.py | 15 ++++++++--- rss_parser/models/atom/entry.py | 17 ++++++------ rss_parser/models/atom/feed.py | 26 ++++++++++++------- rss_parser/models/rss/__init__.py | 9 +++++-- rss_parser/models/rss/channel.py | 43 ++++++++++++++++++------------- rss_parser/models/rss/item.py | 38 +++++++++++++++------------ rss_parser/models/rss/rss.py | 15 ++++++++--- rss_parser/models/rss/skip.py | 19 ++++++++++++++ 9 files changed, 136 insertions(+), 62 deletions(-) create mode 100644 rss_parser/models/rss/skip.py diff --git a/rss_parser/models/__init__.py b/rss_parser/models/__init__.py index a885891..cde00f3 100644 --- a/rss_parser/models/__init__.py +++ b/rss_parser/models/__init__.py @@ -6,7 +6,21 @@ class XMLBaseModel(BaseModel): - model_config = ConfigDict(alias_generator=camel_case) + """ + Base model for all XML-backed schemas. + + - Aliases are generated in camelCase to match common XML tag naming (``pub_date`` -> ``pubDate``). + - Fields can also be populated by their python names, which is handy in tests and fixtures. + - Unknown tags are *kept*, not discarded: anything that is not declared on the schema + (e.g. ``itunes:keywords`` on a bare RSS schema) is stored on the model and is accessible + via ``model_extra``. + """ + + model_config = ConfigDict( + alias_generator=camel_case, + populate_by_name=True, + extra="allow", + ) def json_plain(self, **kwargs) -> str: """ diff --git a/rss_parser/models/atom/atom.py b/rss_parser/models/atom/atom.py index 38f6327..2291bc8 100644 --- a/rss_parser/models/atom/atom.py +++ b/rss_parser/models/atom/atom.py @@ -1,14 +1,21 @@ -from typing import Optional +from typing import Generic, Optional from pydantic import Field +from typing_extensions import TypeVar from rss_parser.models import XMLBaseModel from rss_parser.models.atom.feed import Feed from rss_parser.models.types.tag import Tag +FeedT = TypeVar("FeedT", bound=XMLBaseModel, default=Feed) -class Atom(XMLBaseModel): - """Atom 1.0""" + +class Atom(XMLBaseModel, Generic[FeedT]): + """ + Atom 1.0 (https://validator.w3.org/feed/docs/atom.html). + + Generic over the feed type: ``Atom[Feed[MyEntry]]`` or ``Atom[MyFeed]``. + """ version: Optional[Tag[str]] = Field(alias="@version", default=None) - feed: Tag[Feed] + feed: Tag[FeedT] diff --git a/rss_parser/models/atom/entry.py b/rss_parser/models/atom/entry.py index c0e9f20..53fc12a 100644 --- a/rss_parser/models/atom/entry.py +++ b/rss_parser/models/atom/entry.py @@ -4,12 +4,17 @@ from rss_parser.models import XMLBaseModel from rss_parser.models.atom.person import Person +from rss_parser.models.atom.source import Source 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 -class RequiredAtomEntryMixin(XMLBaseModel): +class Entry(XMLBaseModel): + """https://validator.w3.org/feed/docs/atom.html#requiredEntryElements""" + + # Required entry elements + id: Tag[str] "Identifier for the entry." @@ -19,8 +24,8 @@ class RequiredAtomEntryMixin(XMLBaseModel): updated: Tag[DateTimeOrStr] "Indicates when the entry was updated." + # Recommended entry elements -class RecommendedAtomEntryMixin(XMLBaseModel): authors: OnlyList[Tag[Person]] = Field(alias="author", default_factory=OnlyList) "Entry authors." @@ -33,8 +38,8 @@ class RecommendedAtomEntryMixin(XMLBaseModel): summary: Optional[Tag[str]] = None "Conveys a short summary, abstract, or excerpt of the entry. Some feeds use this tag as the main content." + # Optional entry elements -class OptionalAtomEntryMixin(XMLBaseModel): categories: OnlyList[Tag[dict]] = Field(alias="category", default_factory=OnlyList) "Specifies a categories that the entry belongs to." @@ -47,9 +52,5 @@ class OptionalAtomEntryMixin(XMLBaseModel): published: Optional[Tag[DateTimeOrStr]] = None "Indicates when the entry was published." - source: Optional[Tag[str]] = None + source: Optional[Tag[Source]] = 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/atom/feed.py b/rss_parser/models/atom/feed.py index 3b0eb58..cfdc12a 100644 --- a/rss_parser/models/atom/feed.py +++ b/rss_parser/models/atom/feed.py @@ -1,6 +1,7 @@ -from typing import Optional +from typing import Generic, Optional from pydantic import Field +from typing_extensions import TypeVar from rss_parser.models import XMLBaseModel from rss_parser.models.atom.entry import Entry @@ -9,8 +10,19 @@ from rss_parser.models.types.only_list import OnlyList from rss_parser.models.types.tag import Tag +EntryT = TypeVar("EntryT", bound=XMLBaseModel, default=Entry) + + +class Feed(XMLBaseModel, Generic[EntryT]): + """ + https://validator.w3.org/feed/docs/atom.html + + Generic over the entry type, so a custom entry schema is one parametrization away: + ``Feed[MyEntry]``. + """ + + # Required feed elements -class RequiredAtomFeedMixin(XMLBaseModel): id: Tag[str] "Identifies the feed using a universally unique and permanent URI." @@ -20,17 +32,17 @@ class RequiredAtomFeedMixin(XMLBaseModel): updated: Tag[DateTimeOrStr] "Indicates the last time the feed was modified in a significant way." + # Recommended feed elements -class RecommendedAtomFeedMixin(XMLBaseModel): authors: OnlyList[Tag[Person]] = Field(alias="author", default_factory=OnlyList) "Names one author of the feed. A feed may have multiple author elements." links: OnlyList[Tag[str]] = Field(alias="link", default_factory=OnlyList) "The URL to the feed. A feed may have multiple link elements." + # Optional feed elements -class OptionalAtomFeedMixin(XMLBaseModel): - entries: OnlyList[Tag[Entry]] = Field(alias="entry", default_factory=OnlyList) + entries: OnlyList[Tag[EntryT]] = Field(alias="entry", default_factory=OnlyList) "The entries in the feed. A feed may have multiple entry elements." categories: OnlyList[Tag[dict]] = Field(alias="category", default_factory=OnlyList) @@ -54,7 +66,3 @@ class OptionalAtomFeedMixin(XMLBaseModel): 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/rss/__init__.py b/rss_parser/models/rss/__init__.py index 67d9354..e9c102d 100644 --- a/rss_parser/models/rss/__init__.py +++ b/rss_parser/models/rss/__init__.py @@ -1,3 +1,8 @@ -from .rss import RSS +from rss_parser.models.rss.channel import Channel +from rss_parser.models.rss.image import Image +from rss_parser.models.rss.item import Item +from rss_parser.models.rss.rss import RSS +from rss_parser.models.rss.skip import SkipDays, SkipHours +from rss_parser.models.rss.text_input import TextInput -__all__ = ("RSS",) +__all__ = ("RSS", "Channel", "Image", "Item", "SkipDays", "SkipHours", "TextInput") diff --git a/rss_parser/models/rss/channel.py b/rss_parser/models/rss/channel.py index 6bbfbb0..d8affa3 100644 --- a/rss_parser/models/rss/channel.py +++ b/rss_parser/models/rss/channel.py @@ -1,18 +1,29 @@ -from typing import Optional +from typing import Generic, Optional from pydantic import Field +from typing_extensions import TypeVar from rss_parser.models import XMLBaseModel from rss_parser.models.rss.image import Image from rss_parser.models.rss.item import Item +from rss_parser.models.rss.skip import SkipDays, SkipHours from rss_parser.models.rss.text_input import TextInput 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 +ItemT = TypeVar("ItemT", bound=XMLBaseModel, default=Item) -class RequiredChannelElementsMixin(XMLBaseModel): - """https://www.rssboard.org/rss-specification#requiredChannelElements.""" + +class Channel(XMLBaseModel, Generic[ItemT]): + """ + https://www.rssboard.org/rss-specification#requiredChannelElements + + Generic over the item type, so a custom item schema is one parametrization away: + ``Channel[MyItem]``. + """ + + # Required channel elements 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 @@ -23,11 +34,10 @@ class RequiredChannelElementsMixin(XMLBaseModel): description: Tag[str] # The latest news from GoUpstate.com, a Spartanburg Herald-Journal Web site. "Phrase or sentence describing the channel." + # Optional channel elements + # https://www.rssboard.org/rss-specification#optionalChannelElements -class OptionalChannelElementsMixin(XMLBaseModel): - """https://www.rssboard.org/rss-specification#optionalChannelElements.""" - - items: OnlyList[Tag[Item]] = Field(alias="item", default_factory=OnlyList) + items: OnlyList[Tag[ItemT]] = 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 @@ -35,6 +45,7 @@ class OptionalChannelElementsMixin(XMLBaseModel): copyright: Optional[Tag[str]] = None # Copyright 2002, Spartanburg Herald-Journal "Copyright notice for content in the channel." + managing_editor: Optional[Tag[str]] = None # geo@herald.com (George Matesky) "Email address for person responsible for editorial content." web_master: Optional[Tag[str]] = None # betty@herald.com (Betty Guernsey) @@ -47,7 +58,7 @@ class OptionalChannelElementsMixin(XMLBaseModel): "The last time the content of the channel changed." 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 + "Specify one or more categories that the channel belongs to. Follows the same rules as the <item>-level category element." # noqa generator: Optional[Tag[str]] = None # MightyInHouse Content System v2.3 "A string indicating the program used to generate the channel." @@ -56,26 +67,22 @@ class OptionalChannelElementsMixin(XMLBaseModel): "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. The tag is attribute-only - see `.attributes`." # noqa - ttl: Optional[Tag[str]] = None # 60 + ttl: Optional[Tag[int]] = 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 + rating: Optional[Tag[str]] = None "The PICS rating for the channel." - text_input: Optional[Tag[str]] = None + text_input: Optional[Tag[TextInput]] = None "Specifies a text input box that can be displayed with the channel." - skip_hours: Optional[Tag[str]] = None + skip_hours: Optional[Tag[SkipHours]] = 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 - skip_days: Optional[Tag[str]] = None + skip_days: Optional[Tag[SkipDays]] = 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 - - -class Channel(RequiredChannelElementsMixin, OptionalChannelElementsMixin, XMLBaseModel): - pass diff --git a/rss_parser/models/rss/item.py b/rss_parser/models/rss/item.py index 771a0ac..25bb16d 100644 --- a/rss_parser/models/rss/item.py +++ b/rss_parser/models/rss/item.py @@ -1,25 +1,30 @@ from typing import Optional -from pydantic import Field +from pydantic import Field, model_validator from rss_parser.models import XMLBaseModel +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 -class RequiredItemElementsMixin(XMLBaseModel): - title: Tag[str] # Venice Film Festival Tries to Quit Sinking +class Item(XMLBaseModel): + """ + https://www.rssboard.org/rss-specification#hrelementsOfLtitemgt + + All elements of an item are optional, however at least one of title or description + must be present - this is enforced with a validator. + """ + + title: Optional[Tag[str]] = None # Venice Film Festival Tries to Quit Sinking "The title of the item." - links: OnlyList[Tag[str]] = Field(alias="link") # http://nytimes.com/2004/12/07FEST.html - "The URL of the item." + links: OnlyList[Tag[str]] = Field(alias="link", default_factory=OnlyList) # http://nytimes.com/2004/12/07FEST.html + "The URL of the item. Can appear multiple times in the wild, so it's always a list." - 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> + description: Optional[Tag[str]] = None "The item synopsis." - -class OptionalItemElementsMixin(XMLBaseModel): author: Optional[Tag[str]] = None "Email address of the author of the item." @@ -30,18 +35,19 @@ class OptionalItemElementsMixin(XMLBaseModel): "URL of a page for comments relating to the item." 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" + "Describes a media object that is attached to the item. The url/length/type live in `.attributes`.\nCan be a list -> https://validator.w3.org/feed/docs/warning/DuplicateEnclosure.html" # noqa: E501 guid: Optional[Tag[str]] = None "A string that uniquely identifies the item." - pub_date: Optional[Tag[str]] = None + pub_date: Optional[Tag[DateTimeOrStr]] = None # Sat, 07 Sep 2002 00:00:01 GMT "Indicates when the item was published." source: Optional[Tag[str]] = None - "The RSS channel that the item came from." - + "The RSS channel that the item came from. The url of the channel lives in `.attributes`." -class Item(RequiredItemElementsMixin, OptionalItemElementsMixin, XMLBaseModel): - """https://www.rssboard.org/rss-specification#hrelementsOfLtitemgt.""" + @model_validator(mode="after") + def check_title_or_description(self): + if self.title is None and self.description is None: + raise ValueError("either <title> or <description> must be present in an <item>") + return self diff --git a/rss_parser/models/rss/rss.py b/rss_parser/models/rss/rss.py index 13fc9be..4fa848f 100644 --- a/rss_parser/models/rss/rss.py +++ b/rss_parser/models/rss/rss.py @@ -1,14 +1,21 @@ -from typing import Optional +from typing import Generic, Optional from pydantic import Field +from typing_extensions import TypeVar from rss_parser.models import XMLBaseModel from rss_parser.models.rss.channel import Channel from rss_parser.models.types.tag import Tag +ChannelT = TypeVar("ChannelT", bound=XMLBaseModel, default=Channel) -class RSS(XMLBaseModel): - """RSS 2.0.""" + +class RSS(XMLBaseModel, Generic[ChannelT]): + """ + RSS 0.9x / 2.0 (https://www.rssboard.org/rss-specification). + + Generic over the channel type: ``RSS[Channel[MyItem]]`` or ``RSS[MyChannel]``. + """ version: Optional[Tag[str]] = Field(alias="@version", default=None) - channel: Tag[Channel] + channel: Tag[ChannelT] diff --git a/rss_parser/models/rss/skip.py b/rss_parser/models/rss/skip.py new file mode 100644 index 0000000..a40e9b6 --- /dev/null +++ b/rss_parser/models/rss/skip.py @@ -0,0 +1,19 @@ +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 + + +class SkipHours(XMLBaseModel): + """https://www.rssboard.org/skip-hours-days#skiphours.""" + + hours: OnlyList[Tag[int]] = Field(alias="hour", default_factory=OnlyList) + "Up to 24 <hour> values between 0 and 23, representing hours in GMT when aggregators may skip the channel." + + +class SkipDays(XMLBaseModel): + """https://www.rssboard.org/skip-hours-days#skipdays.""" + + days: OnlyList[Tag[str]] = Field(alias="day", default_factory=OnlyList) + "Up to seven <day> values (Monday..Sunday) when aggregators may skip the channel." From 72bfef9c05e9c284c1a7a1c5860f776565a602ea Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:25:01 +0200 Subject: [PATCH 03/20] feat: friendlier Tag ergonomics - str(tag) returns the content ('' for self-closing tags), so print() shows the value instead of the model repr - bool(tag) is False for a tag with no content and no attributes - Attribute access on an empty (self-closing) tag raises a clear AttributeError pointing to .attributes instead of 'NoneType has no attribute ...' --- rss_parser/models/types/tag.py | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/rss_parser/models/types/tag.py b/rss_parser/models/types/tag.py index efd66b8..4f2ab52 100644 --- a/rss_parser/models/types/tag.py +++ b/rss_parser/models/types/tag.py @@ -14,6 +14,9 @@ class Tag(BaseModel, Generic[T]): """ + Generic wrapper around a single XML tag, splitting its text into ``content`` + and its XML attributes into ``attributes``. + >>> from rss_parser.models import XMLBaseModel >>> from rss_parser.models.types.tag import Tag >>> class Model(XMLBaseModel): @@ -26,15 +29,19 @@ class Tag(BaseModel, Generic[T]): >>> # 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.rss.image.Tag[int]'>, <class 'int'>) + >>> # Tags stringify to their content, so print() shows what you expect + >>> str(m.width) + '48' >>> # 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 + >>> # Note that the @ symbol is trimmed from the beginning and the name is converted to snake_case >>> m.category.attributes {'some_attribute': 'https://example.com'} + >>> # Attribute access is forwarded to the content for convenience + >>> m.category.upper() + 'VALID STRING' >>> # 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): @@ -49,8 +56,16 @@ class Tag(BaseModel, Generic[T]): attributes: Dict[str, Any] = Field(default_factory=dict) def __getattr__(self, item): - """Forward default getattr for content for simplicity.""" - return getattr(self.content, item) + """Forward attribute access to the tag's content for simplicity.""" + if item.startswith("__"): # Don't break copy/pickle/inspection protocols + raise AttributeError(item) + content = self.__dict__.get("content") + if content is None: + raise AttributeError( + f"{type(self).__name__} has no attribute {item!r} and its content is empty " + f"(self-closing tag?). XML attributes, if any, are in `.attributes`: {self.attributes!r}" + ) + return getattr(content, item) def __getitem__(self, key): return self.content[key] @@ -58,6 +73,12 @@ def __getitem__(self, key): def __setitem__(self, key, value): self.content[key] = value + def __str__(self): + return "" if self.content is None else str(self.content) + + def __bool__(self): + return self.content is not None or bool(self.attributes) + @model_validator(mode="before") @classmethod def pre_convert(cls, value: Union[T, dict, "Tag[T]"]) -> Union["Tag[T]", Dict[str, Any]]: From b16bbca277bf5d1182f84b8b23ee2541bdb823ba Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:25:02 +0200 Subject: [PATCH 04/20] feat: typed Apple Podcasts (itunes) schema ITunesItemMixin/ITunesChannelMixin expose the itunes:* tags as typed fields, and the ready-made ITunesItem/ITunesChannel/Podcast models compose them with the RSS 2.0 schema. Parsed via PodcastParser or parse(data, parsers={FeedType.RSS: PodcastParser}). --- rss_parser/models/rss/itunes.py | 124 ++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 rss_parser/models/rss/itunes.py diff --git a/rss_parser/models/rss/itunes.py b/rss_parser/models/rss/itunes.py new file mode 100644 index 0000000..e41a15c --- /dev/null +++ b/rss_parser/models/rss/itunes.py @@ -0,0 +1,124 @@ +""" +Apple Podcasts (iTunes) RSS extensions. + +https://podcasters.apple.com/support/823-podcast-requirements + +Use the ready-made ``Podcast`` schema (or ``PodcastParser``) for a typical podcast feed, +or mix ``ITunesChannelMixin``/``ITunesItemMixin`` into your own schemas. +""" + +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.rss.item import Item +from rss_parser.models.rss.rss import RSS +from rss_parser.models.types.only_list import OnlyList +from rss_parser.models.types.tag import Tag + + +class ITunesOwner(XMLBaseModel): + """<itunes:owner> - contact information for the podcast owner.""" + + name: Optional[Tag[str]] = Field(alias="itunes:name", default=None) + "The name of the podcast owner." + + email: Optional[Tag[str]] = Field(alias="itunes:email", default=None) + "The email address of the podcast owner." + + +class ITunesItemMixin(XMLBaseModel): + """Mix into an RSS item schema to get typed access to episode-level itunes:* tags.""" + + itunes_title: Optional[Tag[str]] = Field(alias="itunes:title", default=None) + "An episode title specific for Apple Podcasts, without season/episode numbers." + + itunes_author: Optional[Tag[str]] = Field(alias="itunes:author", default=None) + "The group, person, or people responsible for creating the episode." + + itunes_subtitle: Optional[Tag[str]] = Field(alias="itunes:subtitle", default=None) + "A short description of the episode." + + itunes_summary: Optional[Tag[str]] = Field(alias="itunes:summary", default=None) + "A longer description of the episode." + + itunes_duration: Optional[Tag[str]] = Field(alias="itunes:duration", default=None) + "The duration of the episode - either in seconds or as HH:MM:SS, so it's kept as a string." + + itunes_episode: Optional[Tag[int]] = Field(alias="itunes:episode", default=None) + "The episode number." + + itunes_season: Optional[Tag[int]] = Field(alias="itunes:season", default=None) + "The season number." + + itunes_episode_type: Optional[Tag[str]] = Field(alias="itunes:episodeType", default=None) + "The episode type: full, trailer, or bonus." + + itunes_explicit: Optional[Tag[str]] = Field(alias="itunes:explicit", default=None) + "The episode parental advisory information: true/false (or the legacy yes/no/clean)." + + itunes_image: Optional[Tag[str]] = Field(alias="itunes:image", default=None) + "The episode artwork. The tag is attribute-only - the url lives in `.attributes['href']`." + + itunes_keywords: Optional[Tag[str]] = Field(alias="itunes:keywords", default=None) + "A comma-separated list of keywords (legacy tag, still common in the wild)." + + itunes_block: Optional[Tag[str]] = Field(alias="itunes:block", default=None) + "The episode show or hide status - 'Yes' prevents the episode from appearing in Apple Podcasts." + + +class ITunesChannelMixin(XMLBaseModel): + """Mix into an RSS channel schema to get typed access to show-level itunes:* tags.""" + + itunes_author: Optional[Tag[str]] = Field(alias="itunes:author", default=None) + "The group, person, or people responsible for creating the show." + + itunes_type: Optional[Tag[str]] = Field(alias="itunes:type", default=None) + "The type of show: episodic or serial." + + itunes_title: Optional[Tag[str]] = Field(alias="itunes:title", default=None) + "A show title specific for Apple Podcasts." + + itunes_subtitle: Optional[Tag[str]] = Field(alias="itunes:subtitle", default=None) + "A short description of the show." + + itunes_summary: Optional[Tag[str]] = Field(alias="itunes:summary", default=None) + "A longer description of the show." + + itunes_owner: Optional[Tag[ITunesOwner]] = Field(alias="itunes:owner", default=None) + "The podcast owner contact information." + + itunes_image: Optional[Tag[str]] = Field(alias="itunes:image", default=None) + "The show artwork. The tag is attribute-only - the url lives in `.attributes['href']`." + + itunes_categories: OnlyList[Tag[dict]] = Field(alias="itunes:category", default_factory=OnlyList) + "The show categories. Attribute-only tags, possibly nested - the category name lives in `.attributes['text']`." + + itunes_explicit: Optional[Tag[str]] = Field(alias="itunes:explicit", default=None) + "The show parental advisory information: true/false (or the legacy yes/no/clean)." + + itunes_keywords: Optional[Tag[str]] = Field(alias="itunes:keywords", default=None) + "A comma-separated list of keywords (legacy tag, still common in the wild)." + + itunes_new_feed_url: Optional[Tag[str]] = Field(alias="itunes:new-feed-url", default=None) + "The new podcast RSS feed URL, used when moving a feed." + + itunes_block: Optional[Tag[str]] = Field(alias="itunes:block", default=None) + "The show or hide status - 'Yes' prevents the whole podcast from appearing in Apple Podcasts." + + itunes_complete: Optional[Tag[str]] = Field(alias="itunes:complete", default=None) + "The podcast update status - 'Yes' means the podcast is complete and no new episodes will be published." + + +class ITunesItem(ITunesItemMixin, Item): + """RSS 2.0 item + episode-level Apple Podcasts tags.""" + + +class ITunesChannel(ITunesChannelMixin, Channel[ITunesItem]): + """RSS 2.0 channel + show-level Apple Podcasts tags, with ITunesItem items.""" + + +class Podcast(RSS[ITunesChannel]): + """A ready-made schema for podcast feeds: RSS 2.0 + Apple Podcasts extensions.""" From 1035e5719ed1a479487912d9d11882354b5f2645 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:25:03 +0200 Subject: [PATCH 05/20] feat: RSS 1.0 (RDF Site Summary) models RDF/RDFChannel/RDFItem cover the RSS 1.0 structure where items are siblings of the channel. Module tags (dc:*, syn:*) are kept in model_extra. --- rss_parser/models/rdf/__init__.py | 5 +++++ rss_parser/models/rdf/channel.py | 31 ++++++++++++++++++++++++++++++ rss_parser/models/rdf/item.py | 22 +++++++++++++++++++++ rss_parser/models/rdf/rdf.py | 32 +++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 rss_parser/models/rdf/__init__.py create mode 100644 rss_parser/models/rdf/channel.py create mode 100644 rss_parser/models/rdf/item.py create mode 100644 rss_parser/models/rdf/rdf.py diff --git a/rss_parser/models/rdf/__init__.py b/rss_parser/models/rdf/__init__.py new file mode 100644 index 0000000..7cd4016 --- /dev/null +++ b/rss_parser/models/rdf/__init__.py @@ -0,0 +1,5 @@ +from rss_parser.models.rdf.channel import RDFChannel +from rss_parser.models.rdf.item import RDFItem +from rss_parser.models.rdf.rdf import RDF + +__all__ = ("RDF", "RDFChannel", "RDFItem") diff --git a/rss_parser/models/rdf/channel.py b/rss_parser/models/rdf/channel.py new file mode 100644 index 0000000..542e824 --- /dev/null +++ b/rss_parser/models/rdf/channel.py @@ -0,0 +1,31 @@ +from typing import Optional + +from pydantic import Field + +from rss_parser.models import XMLBaseModel +from rss_parser.models.types.tag import Tag + + +class RDFChannel(XMLBaseModel): + """ + RSS 1.0 <channel> (https://web.resource.org/rss/1.0/spec#s5.3). + + Note that in RSS 1.0 the actual items live *next to* the channel, not inside it - + see ``RDF.items``. The channel's <items> rdf:Seq table of contents, along with any + other non-core tags (dc:*, syn:*), is kept in `model_extra`. + """ + + title: Tag[str] + "A descriptive title for the channel." + + link: Tag[str] + "The URL to which an HTML rendering of the channel title will link." + + description: Tag[str] + "A brief description of the channel's content, function, source, etc." + + image: Optional[Tag[dict]] = None + "An rdf:resource reference to the channel image, if any." + + text_input: Optional[Tag[dict]] = Field(alias="textinput", default=None) + "An rdf:resource reference to the channel textinput, if any." diff --git a/rss_parser/models/rdf/item.py b/rss_parser/models/rdf/item.py new file mode 100644 index 0000000..0632b9b --- /dev/null +++ b/rss_parser/models/rdf/item.py @@ -0,0 +1,22 @@ +from typing import Optional + +from rss_parser.models import XMLBaseModel +from rss_parser.models.types.tag import Tag + + +class RDFItem(XMLBaseModel): + """ + RSS 1.0 <item> (https://web.resource.org/rss/1.0/spec#s5.5). + + Non-core tags (e.g. dc:*) are kept in `model_extra`. + The rdf:about identifier lives in `.attributes` of the wrapping Tag. + """ + + title: Tag[str] + "The item's title." + + link: Tag[str] + "The item's URL." + + description: Optional[Tag[str]] = None + "A brief description/abstract of the item." diff --git a/rss_parser/models/rdf/rdf.py b/rss_parser/models/rdf/rdf.py new file mode 100644 index 0000000..a7bed8c --- /dev/null +++ b/rss_parser/models/rdf/rdf.py @@ -0,0 +1,32 @@ +from typing import Generic, Optional + +from pydantic import Field +from typing_extensions import TypeVar + +from rss_parser.models import XMLBaseModel +from rss_parser.models.rdf.channel import RDFChannel +from rss_parser.models.rdf.item import RDFItem +from rss_parser.models.types.only_list import OnlyList +from rss_parser.models.types.tag import Tag + +RDFChannelT = TypeVar("RDFChannelT", bound=XMLBaseModel, default=RDFChannel) +RDFItemT = TypeVar("RDFItemT", bound=XMLBaseModel, default=RDFItem) + + +class RDF(XMLBaseModel, Generic[RDFChannelT, RDFItemT]): + """ + RSS 1.0 - RDF Site Summary (https://web.resource.org/rss/1.0/spec). + + Unlike RSS 2.0, the items are siblings of the channel, not children of it. + + Generic over the channel and item types: ``RDF[MyChannel, MyItem]``. + """ + + channel: Tag[RDFChannelT] + items: OnlyList[Tag[RDFItemT]] = Field(alias="item", default_factory=OnlyList) + + image: Optional[Tag[dict]] = None + "The channel image, if any." + + text_input: Optional[Tag[dict]] = Field(alias="textinput", default=None) + "The channel textinput, if any." From aa82fdcd67ed5e3905ebaa734ed9f69c412cf90d Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:25:05 +0200 Subject: [PATCH 06/20] feat: universal parse() with feed type detection - parse(data) detects RSS 0.9x/2.0, Atom 1.0 or RSS 1.0 (RDF) from the root element and returns the matching typed model - detect_feed_type(data) exposes detection on its own via the FeedType enum - UnknownFeedTypeError lists the supported roots when detection fails - New parsers: RDFParser (namespace-prefix tolerant) and PodcastParser - BaseParser.parse_dict() allows parsing an existing xmltodict mapping without serializing back to XML --- rss_parser/__init__.py | 26 +++++++- rss_parser/_parser.py | 144 ++++++++++++++++++++++++++++++++++++----- 2 files changed, 151 insertions(+), 19 deletions(-) diff --git a/rss_parser/__init__.py b/rss_parser/__init__.py index 1ae2861..464df68 100644 --- a/rss_parser/__init__.py +++ b/rss_parser/__init__.py @@ -1,3 +1,25 @@ -from ._parser import AtomParser, BaseParser, RSSParser +from rss_parser._parser import ( + DEFAULT_PARSERS, + AtomParser, + BaseParser, + FeedType, + PodcastParser, + RDFParser, + RSSParser, + UnknownFeedTypeError, + detect_feed_type, + parse, +) -__all__ = ("AtomParser", "BaseParser", "RSSParser") +__all__ = ( + "DEFAULT_PARSERS", + "AtomParser", + "BaseParser", + "FeedType", + "PodcastParser", + "RDFParser", + "RSSParser", + "UnknownFeedTypeError", + "detect_feed_type", + "parse", +) diff --git a/rss_parser/_parser.py b/rss_parser/_parser.py index c00e663..251cb9f 100644 --- a/rss_parser/_parser.py +++ b/rss_parser/_parser.py @@ -1,30 +1,46 @@ -from typing import ClassVar, Optional, Type +from enum import Enum +from typing import Any, ClassVar, Dict, Mapping, Optional, Type -from xmltodict import parse +from xmltodict import parse as _xml_to_dict from rss_parser.custom_decorators import abstract_class_attributes from rss_parser.models import XMLBaseModel from rss_parser.models.atom import Atom +from rss_parser.models.rdf import RDF from rss_parser.models.rss import RSS +from rss_parser.models.rss.itunes import Podcast # >>> FUTURE # TODO: May be support generator based approach for big rss feeds # TODO: Add cli to parse to json -# TODO: Possibly bundle as deb/rpm/exe # TODO: Older Atom versions -# TODO: Older RSS versions + + +class FeedType(str, Enum): + RSS = "rss" + "RSS 0.9x / 2.0 - <rss> root element." + + ATOM = "atom" + "Atom 1.0 - <feed> root element." + + RDF = "rdf" + "RSS 1.0 (RDF Site Summary) - <rdf:RDF> root element." + + +class UnknownFeedTypeError(ValueError): + """Raised when the feed type cannot be detected from the XML root element.""" @abstract_class_attributes("schema") class BaseParser: - """Parser for rss/atom files.""" + """Parser for rss/atom/rdf files.""" schema: ClassVar[Type[XMLBaseModel]] - root_key: Optional[str] = None + root_key: ClassVar[Optional[str]] = None @staticmethod - def to_xml(data: str, *args, **kwargs): - return parse(str(data), *args, **kwargs) + def to_xml(data: str, *args, **kwargs) -> Dict[str, Any]: + return _xml_to_dict(str(data), *args, **kwargs) @classmethod def parse( @@ -37,28 +53,122 @@ def parse( """ Parse XML data into schema. :param data: string of XML data that needs to be parsed + :param schema: override the parser's default schema + :param root_key: override the parser's default root key :return: "schema" object """ - root = cls.to_xml(data) + return cls.parse_dict(cls.to_xml(data), schema=schema, root_key=root_key) + @classmethod + def parse_dict( + cls, + root: Mapping[str, Any], + *, + schema: Optional[Type[XMLBaseModel]] = None, + root_key: Optional[str] = None, + ) -> XMLBaseModel: + """Parse an already xmltodict-converted mapping into schema.""" schema = schema if schema else cls.schema - root_key = root_key if root_key else cls.root_key if root_key: root = root.get(root_key, root) - if hasattr(schema, "model_validate"): - return schema.model_validate(root) + return schema.model_validate(root) + + +class RSSParser(BaseParser): + """RSS 0.9x / 2.0 parser.""" - # Pydantic v1 only - return schema.parse_obj(root) + root_key = "rss" + schema = RSS class AtomParser(BaseParser): + """Atom 1.0 parser.""" + schema = Atom -class RSSParser(BaseParser): - root_key = "rss" - schema = RSS +class RDFParser(BaseParser): + """RSS 1.0 (RDF Site Summary) parser.""" + + root_key = "rdf:RDF" + schema = RDF + + @classmethod + def parse_dict( + cls, + root: Mapping[str, Any], + *, + schema: Optional[Type[XMLBaseModel]] = None, + root_key: Optional[str] = None, + ) -> XMLBaseModel: + if root_key is None: + # The rdf namespace prefix is not guaranteed to be "rdf" - find the actual root key + root_key = next( + (key for key in root if key.lower() == "rdf" or key.lower().endswith(":rdf")), + cls.root_key, + ) + return super().parse_dict(root, schema=schema, root_key=root_key) + + +class PodcastParser(RSSParser): + """RSS 2.0 parser with Apple Podcasts (itunes:*) extensions.""" + + schema = Podcast + + +DEFAULT_PARSERS: Dict[FeedType, Type[BaseParser]] = { + FeedType.RSS: RSSParser, + FeedType.ATOM: AtomParser, + FeedType.RDF: RDFParser, +} + + +def _detect_feed_type_from_root(root: Mapping[str, Any]) -> FeedType: + for key in root: + lowered = key.lower() + if lowered == "rss": + return FeedType.RSS + if lowered == "feed": + return FeedType.ATOM + if lowered == "rdf" or lowered.endswith(":rdf"): + return FeedType.RDF + + raise UnknownFeedTypeError( + f"Could not detect the feed type from root element(s) {sorted(root)}. " + "Supported roots are <rss> (RSS 0.9x/2.0), <feed> (Atom 1.0) and <rdf:RDF> (RSS 1.0). " + "If your document uses a custom root, parse it with an explicit parser, e.g. " + "RSSParser.parse(data, schema=..., root_key=...)" + ) + + +def detect_feed_type(data: str) -> FeedType: + """ + Detect the feed type from the XML root element. + + :raises UnknownFeedTypeError: if the root element is not a known feed root + """ + return _detect_feed_type_from_root(BaseParser.to_xml(data)) + + +def parse( + data: str, + *, + parsers: Optional[Mapping[FeedType, Type[BaseParser]]] = None, +) -> XMLBaseModel: + """ + Parse any supported feed, detecting the feed type from the XML root element. + + Returns an ``RSS``, ``Atom`` or ``RDF`` model depending on the detected type. + + :param data: string of XML data that needs to be parsed + :param parsers: optionally override the parser used for a feed type, + e.g. ``parse(data, parsers={FeedType.RSS: PodcastParser})`` + :raises UnknownFeedTypeError: if the root element is not a known feed root + """ + parser_map: Dict[FeedType, Type[BaseParser]] = {**DEFAULT_PARSERS, **(parsers or {})} + root = BaseParser.to_xml(data) + feed_type = _detect_feed_type_from_root(root) + return parser_map[feed_type].parse_dict(root) From 6f1e66376165496c05ac1265de031bcad4bba412 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:26:06 +0200 Subject: [PATCH 07/20] docs: rewrite README and add mkdocs documentation site - README rewritten around the 4.0 API with verified example output - mkdocs-material site: quickstart, parsing guide, schema customization, podcasts, xml handling internals, migration guide, contributing - Deploy workflow publishes to gh-pages on docs changes in master, replacing the stale v1-era page --- .github/workflows/docs.yml | 36 ++++++ .gitignore | 3 + README.md | 225 +++++++++---------------------------- docs/contributing.md | 52 +++++++++ docs/extending.md | 120 ++++++++++++++++++++ docs/index.md | 43 +++++++ docs/migration.md | 66 +++++++++++ docs/parsing.md | 115 +++++++++++++++++++ docs/podcasts.md | 71 ++++++++++++ docs/quickstart.md | 89 +++++++++++++++ docs/xml.md | 106 +++++++++++++++++ mkdocs.yml | 65 +++++++++++ 12 files changed, 818 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/contributing.md create mode 100644 docs/extending.md create mode 100644 docs/index.md create mode 100644 docs/migration.md create mode 100644 docs/parsing.md create mode 100644 docs/podcasts.md create mode 100644 docs/quickstart.md create mode 100644 docs/xml.md create mode 100644 mkdocs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..1c70003 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,36 @@ +name: Deploy docs + +on: + push: + branches: + - master + paths: + - "docs/**" + - "mkdocs.yml" + - ".github/workflows/docs.yml" + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.2.2 + + - name: Install poetry + run: pipx install poetry + + - name: Set up Python + uses: actions/setup-python@v5.6.0 + with: + python-version: "3.12" + cache-dependency-path: pyproject.toml + cache: poetry + + - name: Install dependencies + run: poetry install --with docs + + - name: Deploy to gh-pages + run: poetry run mkdocs gh-deploy --force diff --git a/.gitignore b/.gitignore index bcb9206..4174727 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,6 @@ venv.bak/ .ruff_cache .python-version .DS_Store + +# mkdocs build output +site/ diff --git a/README.md b/README.md index ba47ce6..36b312d 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,21 @@ -# RSS Parser +# rss-parser -[![Downloads](https://pepy.tech/badge/rss-parser)](https://pepy.tech/project/rss-parser) -[![Downloads](https://pepy.tech/badge/rss-parser/month)](https://pepy.tech/project/rss-parser) -[![Downloads](https://pepy.tech/badge/rss-parser/week)](https://pepy.tech/project/rss-parser) +**Typed, pydantic-powered RSS/Atom parsing for Python.** [![PyPI version](https://img.shields.io/pypi/v/rss-parser)](https://pypi.org/project/rss-parser) [![Python versions](https://img.shields.io/pypi/pyversions/rss-parser)](https://pypi.org/project/rss-parser) +[![Downloads](https://pepy.tech/badge/rss-parser)](https://pepy.tech/project/rss-parser) [![Wheel status](https://img.shields.io/pypi/wheel/rss-parser)](https://pypi.org/project/rss-parser) [![License](https://img.shields.io/pypi/l/rss-parser?color=success)](https://github.com/dhvcc/rss-parser/blob/master/LICENSE) -![Docs](https://github.com/dhvcc/rss-parser/actions/workflows/pages/pages-build-deployment/badge.svg) ![CI](https://github.com/dhvcc/rss-parser/actions/workflows/ci.yml/badge.svg?branch=master) +![Docs](https://github.com/dhvcc/rss-parser/actions/workflows/docs.yml/badge.svg) ![PyPi publish](https://github.com/dhvcc/rss-parser/actions/workflows/publish_to_pypi.yml/badge.svg) -## About +`rss-parser` turns RSS/Atom XML into typed [pydantic](https://docs.pydantic.dev) models — +autocomplete, validation, and clear errors instead of digging through nested dicts. -`rss-parser` is a type-safe Python RSS/Atom parsing module built using [pydantic](https://github.com/pydantic/pydantic) and [xmltodict](https://github.com/martinblech/xmltodict). +**[Documentation](https://dhvcc.github.io/rss-parser)** ## Installation @@ -23,68 +23,23 @@ pip install rss-parser ``` -or - -```bash -git clone https://github.com/dhvcc/rss-parser.git -cd rss-parser -poetry build -pip install dist/*.whl -``` - -## V1 -> V2 Migration -- The `Parser` class has been renamed to `RSSParser` -- 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: +## Quickstart ```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 - -**NOTE: For parsing Atom, use `AtomParser`** - -```python -from rss_parser import RSSParser +from rss_parser import parse from requests import get # noqa rss_url = "https://rss.art19.com/apology-line" response = get(rss_url) -rss = RSSParser.parse(response.text) +feed = parse(response.text) # detects RSS 2.0 / 0.9x, Atom 1.0 or RSS 1.0 (RDF) -# Print out rss meta data -print("Language", rss.channel.language) -print("RSS", rss.version) +print("Language", feed.channel.language) +print("RSS", feed.version) -# Iteratively print feed items -for item in rss.channel.items: +for item in feed.channel.items: print(item.title) - print(item.description[:50]) + print(str(item.description)[:50]) # Language en # RSS 2.0 @@ -94,149 +49,71 @@ for item in rss.channel.items: # <p>If you could call a number and say you’re sorry ``` -Here we can see that the description still contains `<p>` tags - this is because it's wrapped in [CDATA](https://www.w3resource.com/xml/CDATA-sections.php) like so: - -``` -<![CDATA[<p>If you could call ...</p>]]> -``` +`parse()` picks the right parser from the XML root element and raises `UnknownFeedTypeError` +if the document is not a feed. If you already know the feed type, use the explicit parsers: +`RSSParser`, `AtomParser`, `RDFParser`, `PodcastParser`. -### Overriding Schema +## Podcasts -If you want to customize the schema or provide a custom one, use the `schema` keyword argument of the parser: +`itunes:*` tags are supported out of the box, fully typed: ```python -from rss_parser import RSSParser -from rss_parser.models import XMLBaseModel -from rss_parser.models.rss import RSS -from rss_parser.models.types import Tag - - -class CustomSchema(RSS, XMLBaseModel): - channel: None = None # Removing previous channel field - custom: Tag[str] +from rss_parser import PodcastParser +podcast = PodcastParser.parse(feed_xml) +channel = podcast.channel.content -with open("tests/samples/custom.xml") as f: - data = f.read() +channel.itunes_author # 'Wondery' +channel.itunes_owner.content.email # 'iwonder@wondery.com' +channel.itunes_image.attributes["href"] # artwork url -rss = RSSParser.parse(data, schema=CustomSchema) - -print("RSS", rss.version) -print("Custom", rss.custom) - -# RSS 2.0 -# Custom Custom tag data +episode = channel.items[0].content +episode.itunes_duration # '00:05:01' +episode.itunes_episode_type # 'trailer' ``` -### xmltodict - -This library uses [xmltodict](https://github.com/martinblech/xmltodict) to parse XML data. You can find the detailed documentation [here](https://github.com/martinblech/xmltodict#xmltodict). +## Custom fields: one subclass away -The key thing to understand is that your data is processed into dictionaries. - -For example, this XML: - -```xml -<tag>content</tag> -``` - -will result in the following dictionary: +The models are generic, so extending the schema doesn't require re-declaring the whole tree: ```python -{ - "tag": "content" -} -``` +from typing import Optional +from pydantic import Field -*However*, when handling attributes, the content of the tag will also be a dictionary: +from rss_parser import RSSParser +from rss_parser.models.rss import RSS, Channel, Item +from rss_parser.models.types import Tag -```xml -<tag attr="1" data-value="data">data</tag> -``` -This becomes: +class MyItem(Item): + dc_creator: Optional[Tag[str]] = Field(alias="dc:creator", default=None) -```python -{ - "tag": { - "@attr": "1", - "@data-value": "data", - "#text": "content" - } -} -``` -Multiple children of a tag will be placed into a list: +rss = RSSParser.parse(data, schema=RSS[Channel[MyItem]]) -```xml -<div> - <tag>content</tag> - <tag>content2</tag> -</div> +rss.channel.items[0].content.dc_creator ``` -This results in a list: +And even without a custom schema, unknown tags are never dropped — they're kept in `model_extra`: ```python -[ - { "tag": "content" }, - { "tag": "content" }, -] +rss = RSSParser.parse(podcast_xml) +rss.channel.content.model_extra["itunes:author"] # 'Wondery' ``` -If you don't want to deal with these conditions and want to parse something **always** as a list, please use `rss_parser.models.types.only_list.OnlyList` like we did in `Channel`: -```python -from typing import Optional - -from pydantic import Field +See [Customizing the schema](https://dhvcc.github.io/rss-parser/extending/) for mixins, +repeatable tags, and the field types cheat sheet. -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 -... +## Migrating from 3.x - -class OptionalChannelElementsMixin(...): - ... - items: Optional[OnlyList[Tag[Item]]] = Field(alias="item", default_factory=list) -``` - -### Tag Field - -This is a generic field that handles tags as raw data or as a dictionary returned with attributes. - -Example: - -```python -from rss_parser.models import XMLBaseModel -from rss_parser.models.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 -assert m.width.content == 48 - -assert type(m.width), type(m.width.content) == (Tag[int], int) - -# The attributes are empty by default -assert m.width.attributes == {} # But are populated when provided. - -# Note that the @ symbol is trimmed from the beginning and the name is converted to snake_case -assert m.category.attributes == {'some_attribute': 'https://example.com'} -``` +4.0 removes the legacy pydantic v1 models, fixes several RSS 2.0 spec violations, and makes the +models generic and lossless. See the +[migration guide](https://dhvcc.github.io/rss-parser/migration/) for the full list. ## Contributing -Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. +Pull requests are welcome. For major changes, please open an issue first to discuss what you +would like to change. Install dependencies with `poetry install` (`pip install poetry`). @@ -246,6 +123,8 @@ Using `pre-commit` is highly recommended. To install hooks, run: poetry run pre-commit install -t=pre-commit -t=pre-push ``` +See [Contributing](https://dhvcc.github.io/rss-parser/contributing/) for tests, snapshots, and docs. + ## License [GPLv3](https://github.com/dhvcc/rss-parser/blob/master/LICENSE) diff --git a/docs/contributing.md b/docs/contributing.md new file mode 100644 index 0000000..fe6c7d6 --- /dev/null +++ b/docs/contributing.md @@ -0,0 +1,52 @@ +# Contributing + +Pull requests are welcome. For major changes, please open an +[issue](https://github.com/dhvcc/rss-parser/issues) first to discuss what you would like to change. + +## Setup + +```bash +pip install poetry +poetry install +``` + +Using `pre-commit` is highly recommended. To install hooks, run: + +```bash +poetry run pre-commit install -t=pre-commit -t=pre-push +``` + +## Running checks + +```bash +poetry run pytest --doctest-modules rss_parser tests +poetry run black --check . +poetry run ruff check . +poetry run mypy rss_parser +``` + +## Sample-based snapshot tests + +Feed samples live in `tests/samples/<kind>/<name>/` where `<kind>` is one of +`rss`, `atom`, `rdf`, `podcast`. Each sample dir contains: + +- `data.xml` — the feed +- `result.json` — the expected `model_dump` output + +Dropping a new sample directory in is enough — the snapshot test discovers it automatically. +To (re)generate `result.json` after adding a sample or intentionally changing model output: + +```bash +poetry run python -m scripts.update_snapshots +``` + +Review the resulting diff carefully — the snapshots are the contract. + +## Docs + +Docs are built with [mkdocs-material](https://squidfunk.github.io/mkdocs-material/): + +```bash +poetry install --with docs +poetry run mkdocs serve +``` diff --git a/docs/extending.md b/docs/extending.md new file mode 100644 index 0000000..aaab8ac --- /dev/null +++ b/docs/extending.md @@ -0,0 +1,120 @@ +# Customizing the schema + +RSS in the wild is full of namespaced extensions (`itunes:*`, `media:*`, `dc:*`, custom vendor tags). +`rss-parser` keeps the default schemas strict and spec-shaped, but makes it cheap to bolt on your own fields. + +## Unknown tags are kept + +Before writing any code, check if you need to: every tag that is not declared on the schema +is preserved in `model_extra`, not thrown away. + +```python +from rss_parser import RSSParser + +rss = RSSParser.parse(podcast_xml) + +rss.channel.content.model_extra["itunes:author"] +# 'Wondery' +``` + +That's untyped, though. For typed access, declare fields. + +## Add typed fields: one subclass, one parametrization + +The core models are **generic**: `Channel` is generic over its item type, `RSS` over its channel type +(`Feed`/`Atom` likewise for Atom entries/feeds). To extend the item schema you subclass `Item` +and parametrize — no need to re-declare the channel or root models: + +```python +from typing import Optional +from pydantic import Field + +from rss_parser import RSSParser +from rss_parser.models.rss import RSS, Channel, Item +from rss_parser.models.types import Tag + + +class MyItem(Item): + media_content: Optional[Tag[dict]] = Field(alias="media:content", default=None) + dc_creator: Optional[Tag[str]] = Field(alias="dc:creator", default=None) + + +rss = RSSParser.parse(data, schema=RSS[Channel[MyItem]]) + +rss.channel.items[0].content.dc_creator +``` + +Channel-level tags work the same way: + +```python +class MyChannel(Channel[MyItem]): + webfeeds_icon: Optional[Tag[str]] = Field(alias="webfeeds:icon", default=None) + + +rss = RSSParser.parse(data, schema=RSS[MyChannel]) +``` + +!!! tip "Namespaced tags need an explicit alias" + Field aliases are generated in camelCase (`pub_date` -> `pubDate`), but XML namespace prefixes + contain a colon, so declare them explicitly: `Field(alias="media:content")`. + +!!! tip "Podcasts are pre-built" + For `itunes:*` tags you don't need any of this — see [Podcasts](podcasts.md). + +## Reusable mixins + +If you use the same extension tags across projects, package them as a mixin +(this is exactly how the built-in iTunes support is implemented): + +```python +from rss_parser.models import XMLBaseModel + + +class MediaRSSMixin(XMLBaseModel): + """https://www.rssboard.org/media-rss""" + + media_content: Optional[Tag[dict]] = Field(alias="media:content", default=None) + media_thumbnail: Optional[Tag[dict]] = Field(alias="media:thumbnail", default=None) + + +class MediaItem(MediaRSSMixin, Item): + pass + + +rss = RSSParser.parse(data, schema=RSS[Channel[MediaItem]]) +``` + +## Field types cheat sheet + +| XML shape | Field declaration | +| --- | --- | +| `<tag>text</tag>` | `Optional[Tag[str]] = None` | +| `<tag>42</tag>` | `Optional[Tag[int]] = None` | +| `<tag attr="x"/>` (attribute-only) | `Optional[Tag[str]] = None`, read `.attributes` | +| Repeatable tag | `OnlyList[Tag[str]] = Field(alias="tag", default_factory=OnlyList)` | +| Tag with children | `Optional[Tag[MyChildModel]] = None` | +| Anything, kept raw | `Optional[Tag[dict]] = None` | + +`OnlyList` (from `rss_parser.models.types`) normalizes the xmltodict quirk where a single +occurrence is a dict but multiple occurrences are a list — with it, the field is *always* a list. + +## Replacing the schema entirely + +The `schema` argument accepts any `XMLBaseModel`, so you can parse arbitrary XML documents +with the same machinery: + +```python +from rss_parser import RSSParser +from rss_parser.models import XMLBaseModel +from rss_parser.models.types import Tag + + +class CustomSchema(XMLBaseModel): + custom: Tag[str] + + +rss = RSSParser.parse('<rss version="2.0"><custom>Custom tag data</custom></rss>', schema=CustomSchema) + +print(rss.custom) +# Custom tag data +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..2a18a6a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,43 @@ +# rss-parser + +**Typed, pydantic-powered RSS/Atom parsing for Python.** + +[![PyPI version](https://img.shields.io/pypi/v/rss-parser)](https://pypi.org/project/rss-parser) +[![Python versions](https://img.shields.io/pypi/pyversions/rss-parser)](https://pypi.org/project/rss-parser) +[![Downloads](https://pepy.tech/badge/rss-parser)](https://pepy.tech/project/rss-parser) +[![License](https://img.shields.io/pypi/l/rss-parser?color=success)](https://github.com/dhvcc/rss-parser/blob/master/LICENSE) + +`rss-parser` turns RSS/Atom XML into typed [pydantic](https://docs.pydantic.dev) models — +so instead of digging through nested dicts, you get autocomplete, validation, and clear errors. + +```python +from rss_parser import parse + +feed = parse(xml_data) # detects RSS 2.0 / 0.9x, Atom 1.0 or RSS 1.0 (RDF) + +print(feed.channel.title) +for item in feed.channel.items: + print(item.title, item.pub_date) +``` + +## Why rss-parser + +- **Typed all the way down** — every tag is a model field with type validation, not a dict key you have to guess. +- **Feed type detection** — `parse()` figures out whether it's RSS 2.0, RSS 0.9x, Atom 1.0 or RSS 1.0 (RDF) from the root element. Explicit per-type parsers are still there when you know what you have. +- **Podcast-ready** — `PodcastParser` gives you typed `itunes:*` fields (duration, episode, season, owner, artwork, categories) out of the box. +- **Easy to extend** — models are generic, so adding your own fields is one subclass and one parametrization: `RSS[Channel[MyItem]]`. See [Customizing the schema](extending.md). +- **Nothing is lost** — tags that are not declared on the schema are kept in `model_extra` instead of being silently dropped. + +## Installation + +```bash +pip install rss-parser +``` + +## Where to go next + +- [Quickstart](quickstart.md) — parse your first feed in a minute. +- [Parsing feeds](parsing.md) — feed types, detection, and the parser classes. +- [Customizing the schema](extending.md) — add custom tags without fighting the library. +- [Podcasts](podcasts.md) — typed Apple Podcasts extensions. +- [Migration](migration.md) — upgrading from 3.x (or older). diff --git a/docs/migration.md b/docs/migration.md new file mode 100644 index 0000000..071b004 --- /dev/null +++ b/docs/migration.md @@ -0,0 +1,66 @@ +# Migration + +## 3.x -> 4.0 + +### Breaking changes + +**Legacy pydantic v1 models are gone.** `rss_parser.models.legacy` and +`rss_parser.pydantic_proxy` have been removed. 4.x requires pydantic >= 2.7. +If you still need pydantic v1, stay on `rss-parser==3.x`. + +**Item elements are optional now (spec-compliant).** Per the RSS 2.0 spec, all elements of an +`<item>` are optional as long as `title` *or* `description` is present. `item.title`, +`item.description` and `item.links` used to be required — code assuming they're always set +should check for `None` (feeds that used to fail validation now parse). + +**Fixed channel fields** (these were wrong/broken in 3.x): + +| Field | 3.x | 4.0 | +| --- | --- | --- | +| `channel.text_input` | `Tag[str]` (crashed on valid feeds) | `Tag[TextInput]` | +| `channel.rating` | `Tag[TextInput]` | `Tag[str]` | +| `channel.skip_hours` | `Tag[str]` (crashed on valid feeds) | `Tag[SkipHours]` (`.hours` list of ints) | +| `channel.skip_days` | `Tag[str]` (crashed on valid feeds) | `Tag[SkipDays]` (`.days` list of strs) | +| `channel.managing_editor` | *missing* | `Tag[str]` | +| `channel.ttl` | `Tag[str]` | `Tag[int]` | +| `item.pub_date` | `Tag[str]` | `Tag[DateTimeOrStr]` | + +**Mixin classes were flattened.** `RequiredChannelElementsMixin`, `OptionalChannelElementsMixin`, +`RequiredItemElementsMixin`, `OptionalItemElementsMixin` and the Atom equivalents no longer exist — +the fields live directly on `Channel`, `Item`, `Feed`, `Entry`. Subclass those instead. + +**`atom.Entry.source`** is now a typed `Tag[Source]` instead of `Tag[str]`. + +**Unknown tags are kept.** Models now use pydantic's `extra="allow"`, so undeclared tags +(e.g. `itunes:*` on the plain RSS schema) show up in `model_dump()` output and in `model_extra` +instead of being dropped. + +**`str(tag)` returns the content.** `print(feed.channel.title)` prints `Some title` instead of +`content='Some title' attributes={}`. Reprs are unchanged. + +### New in 4.0 + +- `parse()` — universal entry point with feed type detection, plus `detect_feed_type()`, + `FeedType` and `UnknownFeedTypeError`. See [Parsing feeds](parsing.md). +- RSS 1.0 (RDF) support: `RDFParser` and `rss_parser.models.rdf`. +- Typed podcast support: `PodcastParser`, `Podcast`, and the iTunes mixins. + See [Podcasts](podcasts.md). +- Generic models: `RSS[Channel[MyItem]]` replaces the subclass-everything dance. + See [Customizing the schema](extending.md). +- Custom schemas need one subclass instead of three; see the field types cheat sheet in + [Customizing the schema](extending.md#field-types-cheat-sheet). + +## 2.x -> 3.0 + +`rss-parser` 3.x upgraded the runtime models to pydantic v2: + +- Models inherit from pydantic v2 `BaseModel` — use `model_dump()`/`model_dump_json()` + instead of `dict()`/`json()`. +- List-like XML fields use `OnlyList[...]` with an automatic `default_factory`, + so they are always lists. + +## 1.x -> 2.0 + +- The `Parser` class was renamed to `RSSParser`. +- RSS-specific models moved from `rss_parser.models` to `rss_parser.models.rss`. +- Date parsing produces `datetime` objects where it previously kept strings. diff --git a/docs/parsing.md b/docs/parsing.md new file mode 100644 index 0000000..ab4b0e8 --- /dev/null +++ b/docs/parsing.md @@ -0,0 +1,115 @@ +# Parsing feeds + +## Supported feed types + +| Feed type | Root element | Parser | Model | +| --- | --- | --- | --- | +| RSS 2.0 / 0.9x | `<rss>` | `RSSParser` | `rss_parser.models.rss.RSS` | +| Atom 1.0 | `<feed>` | `AtomParser` | `rss_parser.models.atom.Atom` | +| RSS 1.0 (RDF) | `<rdf:RDF>` | `RDFParser` | `rss_parser.models.rdf.RDF` | +| Podcast (RSS 2.0 + iTunes) | `<rss>` | `PodcastParser` | `rss_parser.models.rss.itunes.Podcast` | + +RSS 0.91/0.92 feeds are a structural subset of RSS 2.0 and parse with the same model — +check `rss.version` to see what the document declared. + +## Automatic detection + +`parse()` inspects the root element and picks the right parser: + +```python +from rss_parser import parse +from rss_parser.models.rss import RSS +from rss_parser.models.atom import Atom + +feed = parse(data) + +if isinstance(feed, RSS): + print("RSS", feed.version) +elif isinstance(feed, Atom): + print("Atom", feed.feed.title) +``` + +You can also detect without parsing the whole schema: + +```python +from rss_parser import detect_feed_type, FeedType + +detect_feed_type(data) # FeedType.RSS | FeedType.ATOM | FeedType.RDF +``` + +If the document's root is not a known feed root, both raise `UnknownFeedTypeError`: + +```python +from rss_parser import UnknownFeedTypeError + +try: + feed = parse(data) +except UnknownFeedTypeError as e: + print(e) +# Could not detect the feed type from root element(s) ['html']. Supported roots are +# <rss> (RSS 0.9x/2.0), <feed> (Atom 1.0) and <rdf:RDF> (RSS 1.0). ... +``` + +### Overriding the parser per feed type + +`parse()` accepts a `parsers` mapping, so you can e.g. get typed podcast fields +while keeping automatic detection: + +```python +from rss_parser import parse, FeedType, PodcastParser + +feed = parse(data, parsers={FeedType.RSS: PodcastParser}) +``` + +## Explicit parsers + +When you know the feed type, skip detection: + +```python +from rss_parser import RSSParser, AtomParser, RDFParser, PodcastParser + +rss = RSSParser.parse(data) +``` + +Every parser accepts a `schema` override (see [Customizing the schema](extending.md)) +and a `root_key` override for unusual documents: + +```python +rss = RSSParser.parse(data, schema=MySchema, root_key="rss") +``` + +## RSS 1.0 (RDF) specifics + +RSS 1.0 predates RSS 2.0 and has a different shape: the items are *siblings* of the channel, +not children of it. + +```python +from rss_parser import RDFParser + +rdf = RDFParser.parse(data) + +rdf.channel.title # channel metadata +rdf.items # the actual items, at the document root +rdf.items[0].attributes["rdf:about"] # the item's RDF identifier +``` + +Dublin Core (`dc:*`) and syndication (`syn:*`) module tags are not dropped — +they are available via `model_extra`: + +```python +rdf.channel.content.model_extra["dc:publisher"] +``` + +## Strictness and errors + +Validation errors are raised as pydantic `ValidationError` with a precise path to the problem: + +``` +1 validation error for RSS +channel.content.item.0.content + Value error, either <title> or <description> must be present in an <item> [type=value_error, ...] +``` + +Required fields follow the specs: an RSS channel must have `title`, `link` and `description`; +an item must have at least one of `title`/`description`; an Atom feed/entry must have +`id`, `title` and `updated`. diff --git a/docs/podcasts.md b/docs/podcasts.md new file mode 100644 index 0000000..20efb8c --- /dev/null +++ b/docs/podcasts.md @@ -0,0 +1,71 @@ +# Podcasts (iTunes extensions) + +Most real-world RSS is podcast feeds, and podcast feeds live and die by the +[Apple Podcasts (`itunes:*`) tags](https://podcasters.apple.com/support/823-podcast-requirements). +`rss-parser` ships typed support for them — no custom schema required. + +## PodcastParser + +```python +from rss_parser import PodcastParser + +podcast = PodcastParser.parse(feed_xml) +channel = podcast.channel.content + +channel.itunes_author # 'Wondery' +channel.itunes_type # 'serial' +channel.itunes_explicit # 'yes' +channel.itunes_image.attributes["href"] # 'https://.../artwork.jpeg' +channel.itunes_owner.content.email # 'iwonder@wondery.com' +channel.itunes_categories[0].attributes # {'text': 'True Crime'} + +episode = channel.items[0].content +episode.itunes_duration # '00:05:01' (seconds or HH:MM:SS - kept as str) +episode.itunes_episode # 1 (int) +episode.itunes_season # 1 (int) +episode.itunes_episode_type # 'trailer' | 'full' | 'bonus' +episode.itunes_image.attributes["href"] # episode artwork url +``` + +With automatic feed detection: + +```python +from rss_parser import parse, FeedType, PodcastParser + +feed = parse(data, parsers={FeedType.RSS: PodcastParser}) +``` + +## What's included + +**Channel (show) level** — `itunes_author`, `itunes_type`, `itunes_title`, `itunes_subtitle`, +`itunes_summary`, `itunes_owner` (name + email), `itunes_image`, `itunes_categories`, +`itunes_explicit`, `itunes_keywords`, `itunes_new_feed_url`, `itunes_block`, `itunes_complete`. + +**Item (episode) level** — `itunes_title`, `itunes_author`, `itunes_subtitle`, `itunes_summary`, +`itunes_duration`, `itunes_episode`, `itunes_season`, `itunes_episode_type`, `itunes_explicit`, +`itunes_image`, `itunes_keywords`, `itunes_block`. + +## Composing with your own fields + +The podcast schema is built from mixins, so you can combine it with your own extensions: + +```python +from typing import Optional +from pydantic import Field + +from rss_parser import RSSParser +from rss_parser.models.rss import RSS, Channel, Item +from rss_parser.models.rss.itunes import ITunesChannelMixin, ITunesItemMixin +from rss_parser.models.types import Tag + + +class MyEpisode(ITunesItemMixin, Item): + podcast_transcript: Optional[Tag[dict]] = Field(alias="podcast:transcript", default=None) + + +class MyShow(ITunesChannelMixin, Channel[MyEpisode]): + podcast_guid: Optional[Tag[str]] = Field(alias="podcast:guid", default=None) + + +podcast = RSSParser.parse(data, schema=RSS[MyShow]) +``` diff --git a/docs/quickstart.md b/docs/quickstart.md new file mode 100644 index 0000000..7953905 --- /dev/null +++ b/docs/quickstart.md @@ -0,0 +1,89 @@ +# Quickstart + +## Parse a feed + +```python +from rss_parser import parse +from requests import get # noqa + +rss_url = "https://rss.art19.com/apology-line" +response = get(rss_url) + +feed = parse(response.text) + +# Print out feed meta data +print("Language", feed.channel.language) +print("RSS", feed.version) + +# Iteratively print feed items +for item in feed.channel.items: + print(item.title) + print(str(item.description)[:50]) +``` + +``` +Language en +RSS 2.0 +Wondery Presents - Flipping The Bird: Elon vs Twitter +<p>When Elon Musk posted a video of himself arrivi +Introducing: The Apology Line +<p>If you could call a number and say you’re sorry +``` + +`parse()` detects the feed type from the XML root element and returns the matching typed model — +[`RSS`][parsing], [`Atom`][parsing] or [`RDF`][parsing]. If you already know what you're parsing, +use the explicit parser classes instead: + +```python +from rss_parser import RSSParser, AtomParser, RDFParser, PodcastParser + +rss = RSSParser.parse(rss_xml) +atom = AtomParser.parse(atom_xml) +``` + +[parsing]: parsing.md + +!!! note "Description still contains HTML?" + In the output above the description contains `<p>` tags — that's because the feed wraps it in + [CDATA](https://www.w3resource.com/xml/CDATA-sections.php): + + ``` + <![CDATA[<p>If you could call ...</p>]]> + ``` + + `rss-parser` gives you the feed's data as-is; sanitizing embedded HTML is up to you. + +## Tags: content and attributes + +Every XML tag is wrapped in a `Tag` object that separates the text from the XML attributes: + +```python +item = feed.channel.items[0] + +item.title.content # "Wondery Presents - ..." - the typed value +item.title.attributes # {} - dict of XML attributes, @-prefix stripped, snake_cased + +str(item.title) # same as str(item.title.content) +item.title.upper() # attribute access is forwarded to the content +``` + +Self-closing tags like `<enclosure url="..." length="..." type="..."/>` have `content=None` and +their data in `.attributes`: + +```python +enclosure = item.enclosures[0] +enclosure.attributes["url"] # "https://..." +``` + +## Serialize back to JSON + +```python +feed.model_dump() # full structure, Tag objects as {"content": ..., "attributes": ...} +feed.json_plain() # flattened: every Tag becomes just its content value +``` + +## Next steps + +- [Parsing feeds](parsing.md) — feed types and detection in depth. +- [Customizing the schema](extending.md) — add your own fields. +- [Podcasts](podcasts.md) — typed `itunes:*` support. diff --git a/docs/xml.md b/docs/xml.md new file mode 100644 index 0000000..1b6a221 --- /dev/null +++ b/docs/xml.md @@ -0,0 +1,106 @@ +# How XML is handled + +`rss-parser` uses [xmltodict](https://github.com/martinblech/xmltodict) to convert XML into +dictionaries, then validates those with pydantic. Knowing the intermediate shape helps when +declaring custom fields. + +## The xmltodict shape + +A simple tag becomes a plain value: + +```xml +<tag>content</tag> +``` + +```python +{"tag": "content"} +``` + +A tag with attributes becomes a dict — attributes are prefixed with `@`, the text lives under `#text`: + +```xml +<tag attr="1" data-value="data">content</tag> +``` + +```python +{ + "tag": { + "@attr": "1", + "@data-value": "data", + "#text": "content", + } +} +``` + +A repeated tag becomes a list: + +```xml +<div> + <tag>content</tag> + <tag>content2</tag> +</div> +``` + +```python +{"div": {"tag": ["content", "content2"]}} +``` + +## The Tag field + +`Tag[T]` absorbs the first two shapes so you never touch `@`/`#text` keys: + +```python +from rss_parser.models import XMLBaseModel +from rss_parser.models.types import Tag + + +class Model(XMLBaseModel): + width: Tag[int] + category: Tag[str] + + +m = Model.model_validate( + { + "width": 48, + "category": {"@someAttribute": "https://example.com", "#text": "valid string"}, + } +) + +m.width.content # 48 - typed per the generic argument +m.width.attributes # {} +m.category.attributes # {'some_attribute': 'https://example.com'} +str(m.category) # 'valid string' +m.category.upper() # 'VALID STRING' - attribute access is forwarded to content +``` + +Attribute keys have the `@` stripped and are converted to snake_case. + +For self-closing tags (`<cloud domain="..."/>`), `content` is `None` and everything is +in `.attributes`. Accessing a forwarded attribute on an empty tag raises a clear `AttributeError` +telling you to look at `.attributes`. + +## The OnlyList field + +`OnlyList[T]` absorbs the third shape — a field declared with it is always a list, whether the +tag appeared once, many times, or not at all: + +```python +from pydantic import Field +from rss_parser.models.types import OnlyList, Tag + + +class MyChannel(XMLBaseModel): + items: OnlyList[Tag[Item]] = Field(alias="item", default_factory=OnlyList) +``` + +## Dates + +Fields typed `Tag[DateTimeOrStr]` (like `pub_date`) try RFC 822 first (the RSS standard), +then ISO 8601/timestamps. If nothing matches, the raw string is kept instead of erroring — +dates in the wild are too messy to reject a whole feed over one of them. + +## Aliases + +Field names are snake_case in Python and camelCase in XML; the alias generator handles the +conversion (`pub_date` -> `pubDate`, `managing_editor` -> `managingEditor`). +Namespaced tags (with `:`) always need an explicit alias: `Field(alias="itunes:duration")`. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..8f19769 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,65 @@ +site_name: rss-parser +site_description: Typed pythonic RSS/Atom parser +site_url: https://dhvcc.github.io/rss-parser/ +repo_url: https://github.com/dhvcc/rss-parser +repo_name: dhvcc/rss-parser +edit_uri: edit/master/docs/ + +theme: + name: material + icon: + repo: fontawesome/brands/github + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: deep orange + accent: deep orange + toggle: + icon: material/weather-night + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: deep orange + accent: deep orange + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - navigation.sections + - navigation.expand + - navigation.top + - content.code.copy + - content.action.edit + - search.suggest + - search.highlight + +nav: + - Home: index.md + - Quickstart: quickstart.md + - Guide: + - Parsing feeds: parsing.md + - Customizing the schema: extending.md + - Podcasts (iTunes): podcasts.md + - How XML is handled: xml.md + - Migration: migration.md + - Contributing: contributing.md + +markdown_extensions: + - admonition + - pymdownx.details + - pymdownx.superfences + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - toc: + permalink: true + +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/dhvcc + - icon: fontawesome/brands/python + link: https://pypi.org/project/rss-parser/ From 9803c93929ed8b3757d671ef26d123e52691beb8 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:26:07 +0200 Subject: [PATCH 08/20] chore: bump to 4.0.0, update deps and CI - pydantic >=2.7 (generic model defaults), explicit typing-extensions dep, xmltodict constraint relaxed to >=0.13 - types-xmltodict moved to dev deps; optional docs dependency group - ruff config migrated to the lint.* sections, py39 targets - CI runs package doctests and skips runs for docs-only changes --- .github/workflows/ci.yml | 4 +- poetry.lock | 694 ++++++++++++++++++++++++++++++++++++++- pyproject.toml | 36 +- 3 files changed, 713 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e701b79..104a62b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,8 @@ on: - "README.md" - "pre-commit-config.yaml" - "LICENSE" + - "docs/**" + - "mkdocs.yml" jobs: test: @@ -48,4 +50,4 @@ jobs: run: poetry run mypy rss_parser - name: Test code with pytest - run: poetry run pytest --doctest-modules + run: poetry run pytest --doctest-modules rss_parser tests diff --git a/poetry.lock b/poetry.lock index 67d58c4..35ac225 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -6,6 +6,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -17,6 +18,7 @@ version = "3.0.1" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "asttokens-3.0.1-py3-none-any.whl", hash = "sha256:15a3ebc0f43c2d0a50eeafea25e19046c68398e487b9f1f5b517f7c0f40f976a"}, {file = "asttokens-3.0.1.tar.gz", hash = "sha256:71a4ee5de0bde6a31d64f6b13f2293ac190344478f081c3d1bccfcf5eacb0cb7"}, @@ -26,12 +28,48 @@ files = [ astroid = ["astroid (>=2,<5)"] test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] +[[package]] +name = "babel" +version = "2.18.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, + {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + +[[package]] +name = "backrefs" +version = "6.2" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8"}, + {file = "backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be"}, + {file = "backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90"}, + {file = "backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b"}, + {file = "backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7"}, + {file = "backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7"}, + {file = "backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49"}, +] + +[package.extras] +extras = ["regex"] + [[package]] name = "black" version = "25.11.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {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"}, @@ -77,23 +115,140 @@ d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "certifi" +version = "2026.7.22" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, + {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, +] + [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] +[[package]] +name = "charset-normalizer" +version = "3.4.9" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee"}, + {file = "charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1"}, + {file = "charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0"}, + {file = "charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115"}, + {file = "charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b"}, + {file = "charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177"}, + {file = "charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win32.whl", hash = "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b"}, + {file = "charset_normalizer-3.4.9-cp39-cp39-win_arm64.whl", hash = "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe"}, + {file = "charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5"}, + {file = "charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b"}, +] + [[package]] name = "click" version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" +groups = ["dev", "docs"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -108,10 +263,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {dev = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "decorator" @@ -119,6 +276,7 @@ version = "5.2.1" description = "Decorators for Humans" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, @@ -130,6 +288,7 @@ version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, @@ -141,6 +300,8 @@ version = "1.3.0" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -158,13 +319,14 @@ version = "2.2.1" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {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] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] name = "filelock" @@ -172,17 +334,37 @@ version = "3.19.1" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, ] +[[package]] +name = "ghp-import" +version = "2.1.0" +description = "Copy your docs directly to the gh-pages branch." +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, + {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, +] + +[package.dependencies] +python-dateutil = ">=2.8.1" + +[package.extras] +dev = ["flake8", "markdown", "twine", "wheel"] + [[package]] name = "identify" version = "2.6.15" description = "File identification library for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757"}, {file = "identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf"}, @@ -191,12 +373,53 @@ files = [ [package.extras] license = ["ukkonen"] +[[package]] +name = "idna" +version = "3.18" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, + {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, +] + +[package.extras] +all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version == \"3.9\"" +files = [ + {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, + {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=3.4)"] +perf = ["ipython"] +test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] + [[package]] name = "iniconfig" version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -208,6 +431,7 @@ version = "8.18.1" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, @@ -245,6 +469,7 @@ version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -258,12 +483,50 @@ docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alab qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["docs"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markdown" +version = "3.9" +description = "Python implementation of John Gruber's Markdown." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"}, + {file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] +testing = ["coverage", "pyyaml"] + [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -282,12 +545,112 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markupsafe" +version = "3.0.3" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, + {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, + {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, + {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, + {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, + {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, + {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, + {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, + {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, + {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, + {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, + {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, + {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, + {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, + {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, + {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, + {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, + {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, + {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, + {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, + {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, + {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, + {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, + {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, + {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, + {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, + {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, + {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, + {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, + {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, + {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, + {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, + {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, + {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, + {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, + {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, + {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, + {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, + {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, + {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, +] + [[package]] name = "matplotlib-inline" version = "0.2.1" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "matplotlib_inline-0.2.1-py3-none-any.whl", hash = "sha256:d56ce5156ba6085e00a9d54fead6ed29a9c47e215cd1bba2e976ef39f5710a76"}, {file = "matplotlib_inline-0.2.1.tar.gz", hash = "sha256:e1ee949c340d771fc39e241ea75683deb94762c8fa5f2927ec57c83c4dffa9fe"}, @@ -305,17 +668,123 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "mergedeep" +version = "1.3.4" +description = "A deep merge function for 🐍." +optional = false +python-versions = ">=3.6" +groups = ["docs"] +files = [ + {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, + {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +description = "Project documentation with Markdown." +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, + {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, +] + +[package.dependencies] +click = ">=7.0" +colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} +ghp-import = ">=1.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +jinja2 = ">=2.11.1" +markdown = ">=3.3.6" +markupsafe = ">=2.0.1" +mergedeep = ">=1.3.4" +mkdocs-get-deps = ">=0.2.0" +packaging = ">=20.5" +pathspec = ">=0.11.1" +pyyaml = ">=5.1" +pyyaml-env-tag = ">=0.1" +watchdog = ">=2.0" + +[package.extras] +i18n = ["babel (>=2.9.0)"] +min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, + {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} +mergedeep = ">=1.3.4" +platformdirs = ">=2.2.0" +pyyaml = ">=5.1" + +[[package]] +name = "mkdocs-material" +version = "9.7.7" +description = "Documentation that simply works" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_material-9.7.7-py3-none-any.whl", hash = "sha256:8ea9bb1737a5b524a5f9dcf2e1b4ebda8274ae3008aa7845720a97083bef708f"}, + {file = "mkdocs_material-9.7.7.tar.gz", hash = "sha256:c0649c065b1b0512d60aad8c10f947f8e455284475239b364b610f2deb4d0855"}, +] + +[package.dependencies] +babel = ">=2.10" +backrefs = ">=5.7.post1" +colorama = ">=0.4" +jinja2 = ">=3.1" +markdown = ">=3.2" +mkdocs = ">=1.6,<2" +mkdocs-material-extensions = ">=1.3" +paginate = ">=0.5" +pygments = ">=2.16" +pymdown-extensions = ">=10.2" +requests = ">=2.30" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4)"] +imaging = ["cairosvg (>=2.6)", "pillow (>=10.2)"] +recommended = ["mkdocs-minify-plugin (>=0.7)", "mkdocs-redirects (>=1.2)", "mkdocs-rss-plugin (>=1.6)"] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +description = "Extension pack for Python Markdown and MkDocs Material." +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, + {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, +] + [[package]] name = "mypy" version = "1.18.2" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {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"}, @@ -376,6 +845,7 @@ version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, @@ -387,6 +857,7 @@ version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["dev"] files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -398,17 +869,35 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + [[package]] name = "parso" version = "0.8.5" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "parso-0.8.5-py2.py3-none-any.whl", hash = "sha256:646204b5ee239c396d040b90f9e272e9a8017c630092bf59980beb62fd033887"}, {file = "parso-0.8.5.tar.gz", hash = "sha256:034d7354a9a018bdce352f48b2a8a450f05e9d6ee85db84764e9b6bd96dafe5a"}, @@ -424,6 +913,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -435,6 +925,8 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -449,6 +941,7 @@ 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" +groups = ["dev", "docs"] files = [ {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, @@ -465,6 +958,7 @@ version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, @@ -480,6 +974,7 @@ version = "2.21.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, @@ -498,6 +993,7 @@ version = "3.0.52" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955"}, {file = "prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855"}, @@ -512,6 +1008,8 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -523,6 +1021,7 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -537,6 +1036,7 @@ version = "2.12.4" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e"}, {file = "pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac"}, @@ -550,7 +1050,7 @@ typing-inspection = ">=0.4.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -558,6 +1058,7 @@ version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {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"}, @@ -691,6 +1192,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -699,12 +1201,32 @@ files = [ [package.extras] windows-terminal = ["colorama (>=0.4.6)"] +[[package]] +name = "pymdown-extensions" +version = "10.21.3" +description = "Extension pack for Python Markdown." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6"}, + {file = "pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354"}, +] + +[package.dependencies] +markdown = ">=3.6" +pyyaml = "*" + +[package.extras] +extra = ["pygments (>=2.19.1)"] + [[package]] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, @@ -721,12 +1243,28 @@ 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 = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["docs"] +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[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" +groups = ["dev"] files = [ {file = "pytokens-0.3.0-py3-none-any.whl", hash = "sha256:95b2b5eaf832e469d141a378872480ede3f251a5a5041b8ec6e581d3ac71bbf3"}, {file = "pytokens-0.3.0.tar.gz", hash = "sha256:2f932b14ed08de5fcf0b391ace2642f858f1394c0857202959000b68ed7a458a"}, @@ -741,6 +1279,7 @@ version = "6.0.3" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["dev", "docs"] files = [ {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"}, @@ -817,12 +1356,50 @@ files = [ {file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"}, ] +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +description = "A custom YAML tag for referencing environment variables in YAML files." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, + {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, +] + +[package.dependencies] +pyyaml = "*" + +[[package]] +name = "requests" +version = "2.32.5" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, + {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "rich" 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" +groups = ["dev"] files = [ {file = "rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd"}, {file = "rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4"}, @@ -841,6 +1418,7 @@ version = "0.14.5" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {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"}, @@ -863,12 +1441,25 @@ files = [ {file = "ruff-0.14.5.tar.gz", hash = "sha256:8d3b48d7d8aad423d3137af7ab6c8b1e38e4de104800f0d596990f6ada1a9fc1"}, ] +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["docs"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + [[package]] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -888,6 +1479,8 @@ version = "2.3.0" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" +groups = ["dev"] +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, @@ -939,6 +1532,7 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -954,6 +1548,7 @@ version = "0.14.0.20241009" description = "Typing stubs for xmltodict" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "types-xmltodict-0.14.0.20241009.tar.gz", hash = "sha256:9224c2422c5b6359cf826685b4ee50b14dc2cb9134561ab793ef6b03dd7108e1"}, {file = "types_xmltodict-0.14.0.20241009-py3-none-any.whl", hash = "sha256:92812e17ffa9171416b35806cb5f4ed3f8f52b6724b2c555e4733e902ef4afd0"}, @@ -965,6 +1560,7 @@ version = "4.15.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, @@ -976,6 +1572,7 @@ version = "0.4.2" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7"}, {file = "typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464"}, @@ -984,12 +1581,31 @@ files = [ [package.dependencies] typing-extensions = ">=4.12.0" +[[package]] +name = "urllib3" +version = "2.6.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, + {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, +] + +[package.extras] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] + [[package]] name = "virtualenv" version = "20.35.4" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "virtualenv-20.35.4-py3-none-any.whl", hash = "sha256:c21c9cede36c9753eeade68ba7d523529f228a403463376cf821eaae2b650f1b"}, {file = "virtualenv-20.35.4.tar.gz", hash = "sha256:643d3914d73d3eeb0c552cbb12d7e82adf0e504dbf86a3182f8771a153a1971c"}, @@ -1003,7 +1619,50 @@ 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)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "watchdog" +version = "6.0.0" +description = "Filesystem events monitoring" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, + {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, + {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, + {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, + {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, + {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, + {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, + {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, + {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, + {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, + {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, + {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, + {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, +] + +[package.extras] +watchmedo = ["PyYAML (>=3.10)"] [[package]] name = "wcwidth" @@ -1011,6 +1670,7 @@ version = "0.2.14" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, @@ -1022,12 +1682,34 @@ version = "0.13.0" description = "Makes working with XML feel like you are working with JSON" optional = false python-versions = ">=3.4" +groups = ["main"] files = [ {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, ] +[[package]] +name = "zipp" +version = "3.23.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +markers = "python_version == \"3.9\"" +files = [ + {file = "zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc"}, + {file = "zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.9" -content-hash = "fe37607bd29ea8b94e49ee801bc401f8f0b627967e08e01fca92fc437781e2f2" +content-hash = "79cc47fc9b52e42b67dffd8b1478715fc6f0598604490db3c2db33274a66182b" diff --git a/pyproject.toml b/pyproject.toml index 901bd6c..ae285da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "rss-parser" -version = "3.0.0" +version = "4.0.0" description = "Typed pythonic RSS/Atom parser" authors = ["dhvcc <1337kwiz@gmail.com>"] license = "GPL-3.0" @@ -41,9 +41,9 @@ packages = [{ include = "rss_parser" }, { include = "rss_parser/py.typed" }] [tool.poetry.dependencies] python = "^3.9" -pydantic = "<3.0" -xmltodict = "^0.13.0" -types-xmltodict = "^0.14.0.20241009" +pydantic = ">=2.7,<3.0" +xmltodict = ">=0.13.0" +typing-extensions = ">=4.6" [tool.poetry.group.dev.dependencies] ipython = "*" @@ -53,6 +53,13 @@ ruff = "*" rich = "*" pytest = "^7.4.0" mypy = "*" +types-xmltodict = "^0.14.0.20241009" + +[tool.poetry.group.docs] +optional = true + +[tool.poetry.group.docs.dependencies] +mkdocs-material = "^9.5" [tool.pytest.ini_options] addopts = "--color=yes" @@ -63,12 +70,14 @@ log_level = "INFO" [tool.black] line-length = 120 -target-version = ["py38"] +target-version = ["py39"] [tool.ruff] line-length = 120 -target-version = "py38" +target-version = "py39" respect-gitignore = true + +[tool.ruff.lint] select = [ "PL", # pylint "F", # pyflakes @@ -87,15 +96,14 @@ select = [ "RUF", # ruff ] -[tool.ruff.lint.pep8-naming] -ignore-names = ["LegacyRSS"] - -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "tests/**.py" = [ - "S101", # Use of assert detected - "ARG001", # Unused function argument - "S311", # Allow use of random - "S301", # Allow use of pickle + "S101", # Use of assert detected + "ARG001", # Unused function argument + "PLR2004", # Magic values in assertions are fine +] +"scripts/**.py" = [ + "E402", # sys.path setup before imports ] "**/__init__.py" = ["F401"] "rss_parser/models/atom/**" = ["A003"] From ef0c46def2d1872b6d4cc1a022edeb2e366d92bc Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Thu, 23 Jul 2026 00:26:21 +0200 Subject: [PATCH 09/20] test: JSON snapshot suite and coverage for 4.0 features - Samples reorganized into tests/samples/<kind>/<name>/ and auto-discovered; a new sample dir is picked up without touching test code - Opaque result.pkl snapshots replaced with reviewable result.json (regenerate with: python -m scripts.update_snapshots) - New samples: RSS 0.91 and RSS 1.0 (RDF) - New suites: spec compliance, extensibility/generics, itunes, feed detection, Tag ergonomics --- scripts/update_snapshots.py | 31 + tests/conftest.py | 42 +- tests/samples/apology_line/result.pkl | Bin 5644 -> 0 bytes tests/samples/atom/{ => atom}/data.xml | 0 tests/samples/atom/atom/result.json | 149 + .../{ => atom}/generic_atom_feed/data.xml | 0 .../atom/generic_atom_feed/result.json | 253 ++ tests/samples/atom/result.pkl | Bin 1510 -> 0 bytes tests/samples/generic_atom_feed/result.pkl | Bin 4019 -> 0 bytes tests/samples/github-49/result.pkl | Bin 26979 -> 0 bytes .../{ => podcast}/apology_line/data.xml | 0 .../samples/podcast/apology_line/result.json | 292 ++ tests/samples/rdf/rdf_1_0/data.xml | 56 + tests/samples/rdf/rdf_1_0/result.json | 120 + tests/samples/{ => rss}/github-49/data.xml | 0 tests/samples/rss/github-49/result.json | 3185 +++++++++++++++++ tests/samples/rss/rss_0_91/data.xml | 40 + tests/samples/rss/rss_0_91/result.json | 176 + tests/samples/{ => rss}/rss_2/data.xml | 0 tests/samples/rss/rss_2/result.json | 451 +++ .../{ => rss}/rss_2_no_category_attr/data.xml | 0 .../rss/rss_2_no_category_attr/result.json | 447 +++ .../{ => rss}/rss_2_with_1_item/data.xml | 0 .../samples/rss/rss_2_with_1_item/result.json | 139 + tests/samples/rss_2/result.pkl | Bin 7370 -> 0 bytes .../samples/rss_2_no_category_attr/result.pkl | Bin 7320 -> 0 bytes tests/samples/rss_2_with_1_item/result.pkl | Bin 1905 -> 0 bytes tests/test_detection.py | 67 + tests/test_extensibility.py | 75 + tests/test_itunes.py | 77 + tests/test_parsing.py | 94 - tests/test_snapshots.py | 23 + tests/test_spec_compliance.py | 94 + tests/test_tag.py | 73 + 34 files changed, 5779 insertions(+), 105 deletions(-) create mode 100644 scripts/update_snapshots.py delete mode 100644 tests/samples/apology_line/result.pkl rename tests/samples/atom/{ => atom}/data.xml (100%) create mode 100644 tests/samples/atom/atom/result.json rename tests/samples/{ => atom}/generic_atom_feed/data.xml (100%) create mode 100644 tests/samples/atom/generic_atom_feed/result.json delete mode 100644 tests/samples/atom/result.pkl delete mode 100644 tests/samples/generic_atom_feed/result.pkl delete mode 100644 tests/samples/github-49/result.pkl rename tests/samples/{ => podcast}/apology_line/data.xml (100%) create mode 100644 tests/samples/podcast/apology_line/result.json create mode 100644 tests/samples/rdf/rdf_1_0/data.xml create mode 100644 tests/samples/rdf/rdf_1_0/result.json rename tests/samples/{ => rss}/github-49/data.xml (100%) create mode 100644 tests/samples/rss/github-49/result.json create mode 100644 tests/samples/rss/rss_0_91/data.xml create mode 100644 tests/samples/rss/rss_0_91/result.json rename tests/samples/{ => rss}/rss_2/data.xml (100%) create mode 100644 tests/samples/rss/rss_2/result.json rename tests/samples/{ => rss}/rss_2_no_category_attr/data.xml (100%) create mode 100644 tests/samples/rss/rss_2_no_category_attr/result.json rename tests/samples/{ => rss}/rss_2_with_1_item/data.xml (100%) create mode 100644 tests/samples/rss/rss_2_with_1_item/result.json delete mode 100644 tests/samples/rss_2/result.pkl delete mode 100644 tests/samples/rss_2_no_category_attr/result.pkl delete mode 100644 tests/samples/rss_2_with_1_item/result.pkl create mode 100644 tests/test_detection.py create mode 100644 tests/test_extensibility.py create mode 100644 tests/test_itunes.py delete mode 100644 tests/test_parsing.py create mode 100644 tests/test_snapshots.py create mode 100644 tests/test_spec_compliance.py create mode 100644 tests/test_tag.py diff --git a/scripts/update_snapshots.py b/scripts/update_snapshots.py new file mode 100644 index 0000000..68aabc1 --- /dev/null +++ b/scripts/update_snapshots.py @@ -0,0 +1,31 @@ +""" +Regenerate tests/samples/**/result.json snapshots from data.xml files. + +Run from the repo root after intentionally changing model output: + + python -m scripts.update_snapshots +""" + +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from tests.conftest import PARSERS_BY_KIND, dump_for_snapshot, iter_samples, read_sample + + +def main() -> None: + for kind, sample_dir in iter_samples(): + parser = PARSERS_BY_KIND[kind] + parsed = parser.parse(read_sample(sample_dir)) + snapshot = dump_for_snapshot(parsed) + (sample_dir / "result.json").write_text( + json.dumps(snapshot, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + sys.stdout.write(f"updated {sample_dir.relative_to(sample_dir.parent.parent.parent)}\n") + + +if __name__ == "__main__": + main() diff --git a/tests/conftest.py b/tests/conftest.py index 0746011..88e1b43 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,20 +1,40 @@ -import pickle +import json from pathlib import Path +from typing import Dict, Iterator, Tuple, Type -import pytest +from rss_parser import AtomParser, BaseParser, PodcastParser, RDFParser, RSSParser # Get relative path to samples dir no matter the working dir -sample_dir = Path(__file__).parent.resolve() / "samples" +SAMPLES_DIR = Path(__file__).parent.resolve() / "samples" +# Samples are grouped by feed kind: tests/samples/<kind>/<name>/data.xml + result.json. +# Adding a new sample dir is enough for it to be picked up by the snapshot tests. +PARSERS_BY_KIND: Dict[str, Type[BaseParser]] = { + "rss": RSSParser, + "atom": AtomParser, + "rdf": RDFParser, + "podcast": PodcastParser, +} -@pytest.fixture -def sample_and_result(request): - sample_name = request.param[0] - with open(sample_dir / sample_name / "data.xml", encoding="utf-8") as sample_file: - sample = sample_file.read() +def iter_samples() -> Iterator[Tuple[str, Path]]: + """Yield (kind, sample_dir) for every sample that has a data.xml.""" + for kind_dir in sorted(SAMPLES_DIR.iterdir()): + if not kind_dir.is_dir(): + continue + for sample_dir in sorted(kind_dir.iterdir()): + if (sample_dir / "data.xml").is_file(): + yield kind_dir.name, sample_dir - with open(sample_dir / sample_name / "result.pkl", "rb") as result_file: - result = pickle.load(result_file) - return sample, result +def read_sample(sample_dir: Path) -> str: + return (sample_dir / "data.xml").read_text(encoding="utf-8") + + +def read_snapshot(sample_dir: Path) -> dict: + return json.loads((sample_dir / "result.json").read_text(encoding="utf-8")) + + +def dump_for_snapshot(model) -> dict: + # mode="json" so that datetimes and other rich types compare as their JSON form + return model.model_dump(mode="json", by_alias=True) diff --git a/tests/samples/apology_line/result.pkl b/tests/samples/apology_line/result.pkl deleted file mode 100644 index 57dd6be0644b8ce78025f45dae9e825ab817b7e6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5644 zcmeI0%WmAr6^5Ni@-0Xv$s(%+Q&Et~AR~6O*;gvQK=M@{tF73w<r%<e6v<+<+$4*x zDt5aK1n>iJ*;xe0Yy;#q@)lXfi#$jEDzbeUk1-RB5oBSZAvG^`>eM->{_m8YZ~plQ zzc^)o{<QkEIx}Zdagnd=MJRI3a=ofHyq3GF&Qq-=4@%7xo|n~`Frj(QQuA`MVMc6m z%`&xmwEFbrt<$vBiIA&tbv~q;O@-tb{%BR54Mmp0x-ySg9;QN-(mt|d$H|tlxKYY< zRsAc`x=_3AcBECS5RsNNY+-y`vWQC->Q+{C+hHPgE6&>H>8$NvyXM_!7m>2}SX(O9 zqEdH!JCGGziHNE8Fo;9uG2a=EM!wS<xzwR?#GKxMjXEwHc|nI7>z6Tp5=gO7OkUjI zOKu$`cNgs8T@iRa9cB-nMkf!RMK=#`+?jrP{lVUEyI1jX92)wAeh|fje&3HrevkUy ze!tThx*nrF^RPSW^=U{~)uvt+%mlfNxM*huo35(UDa)riSyg9xBd^zYy;WJ#YI9oh zs1AB{Ysw?U#V*Wg7UiS@p%$|Jz-{YDilZxjw>RxfmY)~-lRVD$2E)61iSUoylflt_ ze(;5(_ZQv!D--eR$6QScCNnxoc|KcJTQN-)TdA@-Ta>{hLJpg$KRy)s4sko=kS>Ym zdR^j<c3l@A;<)&+_R8wC5~U0ovb?3aPS>ORCnArST#_Rs-IM`w$n}&L1<$ACIALU; z%V?KeOGQrRiX1Pv)=V}ww~TM5ifpM=yX*2;)HZE~Rf1f#b;2B4h*V6M4hFII<x*#p z+NFy~X>E{JKSC{pk{7xu{$%6fsjX9|E)<te5|-C)9F}TE3ZXQM2qkkKF+oI35}qlR z#)L}A=f-=g*Y1*=e<UI9FYRrzNU%SMg71W4T~mTZB?K;#j44H@jJ$aM*OY18f(`o~ z&wu^m`B&sXlZ?t4CN^)gJTeng5=oj-%|oK;j0x=B7O_qyG$K=xGZIUYy>tfav+oKs z&6R|+*gi7$d2S7>f`-hPeq1UI`!QRP3?h&yf^CnLs(O_)B@vJE7tjBqNr4A6w?1h> zo|H_P7~;i1G($3>`JAcOd}CDRHi(j8I97lpQw+f@ST$mS)nYX~oVCbxvxFnMJi?@y z;FzhDnlJZ^uMc;uQ#s8TS-^r?<|1EaqEvNk13uj$m`x{+LQGLXCKJdUg**4e4pM?h zV=V+>UC><D!zfWA<d`snzED!6RL4SQm&qO?Z|2c96lqE%qiMQa$ItqK@WqlL&jn*) z(rUGYd~-|_?DfmG#7){Rf075P_``*EaS3&IA&{4vuQmkDe%Y~!%8+lVOr}oQ6-iZM zq6yy)a&kjNxJhm!gF6Ho$&9yH98V*npbAS{vtnWIBE$(2hHxVRDYAsfiWz~hOH?Dw z+o3|H5pu0e-9&uKH6jBsNKts|Nl7JZp@!ae`!>f`L1Bq++SnH;NhFIeZkxRe1#6aP z^!xhp{^WS?_BMi>Ufj-wC|J%U$pzkLQZl)XdWV1d;`Ss+X+GP&^!DfrZMqKMJtX4@ zdy|Gx8v(DEW{bYzfMLrkI285E|BVA{1$xVf@x!L(3gU|*%27{iXU5nV*l*v-ncqah z$%S?xHK~&=9iGSHy*T(yhK<uLlh9foOGAcojr8Ov<K&}9tK?_n<mZh7|F2+8djp_4 zrcR#?1E&`N)`9PPP6W8pPDtHB82ul>+Ut6*>vs&SlV8?|`Zi8`*M{SeIbZUJ?%hnd zo#A5mSoQbk^UwL6gTu!s*KU`OpWd0@TIQk6$-6n7Y}Ye>d|Wc%xle9kZv)RA;&ykv z!EWDw9nZ-p<K%bsfL|TtT8gL)A(6Wl!Fx40$!%zIT?xr&?~l2OYv+CV&rQqOgP1Hu z3B@WQR3TIqILON^z|J?yrqE>IIb=veS_PQ8WAEo+FhIAk!)Ccyym<aME9&(fW3uKy ze+C>1d6`^?9>+Y^4BE5=8`RD_Cx%iV(%gW<=p(#{M2$uR=oVrGAmU?0@};zz3)5-T z?wKnqt_JE*RQ!^bm5W4>fQ1HJXtPxb(vV<jX(kpq2>}*^#TL0)lg%v6LJ}H$2+lXT zG0IE!j0<2MJhV(O(KC36Q-L10?ip&AFjWk^8VEv`85p|+%5Y^H1++{BL`pZ0Au%9j z$~8hA$#P17O_;Gmvr?ja+aa&HVp)hNTb9~1)(wK~B~r@*yBgD+U~+^~flhXMFnW$` z@R)G0h#3w`u4ZPgS3WkaKqK`woUbr=Rg$ZSSYoquj7Z;0fchC3v!|L|6#}l-ECkvu z1nc&MWd#PA^|6rI=Fb`sfih7t;XkDCL{YLYc{D{*P2OfX`l8oY9x&5dz$IHIOB5vf z5;TfUqoDXx()8v8#|IpMthfU5dnohuQ3S0OSTLtNr9sMoyG4t9VRi(J1_%Y%jCIxH z35^`vWT0_s0`eI_UE^{uqtEaM4F&LQ8%zrv)LF*NZc+&^%&Tv_cRx>ghUOIetPEZ@ z!fP)-02D38I|=`t0m`bg=vJmB8vW+r@<W!_x>}tJMX{88n&{@y@Bi`lTD6<>?x42| zTZEX3vF|DDHx$F1MU9E`3l>Z=ik=In?PPmgeU##25|rrt>T{drc6DxQ+9`8DY^K`U zyL;U68!MZU2+fgv3kUKt!j}!LQ*+I;mwrRH>w|d>j(@L1)BL}nRalHz`3j@_wNtmX zq1pj!vrc>DwL=fzS!d*hp3~_CL#O9=0w)*^hmJS&XfPP|d#=}S2T>0fkvocp!O-gt zh9f$_$$Y@Nz8`smK7iZn2i<^<y78bh=nMlt@J3yKKm*!weVpOlP8f{_LwDF84p;~p z2E!ri^twT>H|!0%-JTb8`(8X6d41RmI{j|P4gHZH48p+gwjP_~xb1@0wEU?NnYRI1 zZ9*Yu@9SJzZ7q0&^Z&RyoiJM*<HQ};?O1ghIfN|W;du>}oA4ZGDRTwkEGFQAG<iH{ suOhm{#kcsL6m>qo_i=hn_OCWRko^PMzc<-iAJG2((N4a8yenJ(0c*w9#{d8T diff --git a/tests/samples/atom/data.xml b/tests/samples/atom/atom/data.xml similarity index 100% rename from tests/samples/atom/data.xml rename to tests/samples/atom/atom/data.xml diff --git a/tests/samples/atom/atom/result.json b/tests/samples/atom/atom/result.json new file mode 100644 index 0000000..b309db3 --- /dev/null +++ b/tests/samples/atom/atom/result.json @@ -0,0 +1,149 @@ +{ + "@version": null, + "feed": { + "content": { + "id": { + "content": "tag:example.org,2003:3", + "attributes": {} + }, + "title": { + "content": "Title", + "attributes": { + "type": "text" + } + }, + "updated": { + "content": "2005-07-31T12:29:29Z", + "attributes": {} + }, + "author": [], + "link": [ + { + "content": null, + "attributes": { + "rel": "alternate", + "type": "text/html", + "hreflang": "en", + "href": "http://example.org/" + } + }, + { + "content": null, + "attributes": { + "rel": "self", + "type": "application/atom+xml", + "href": "http://example.org/feed.atom" + } + } + ], + "entry": [ + { + "content": { + "id": { + "content": "tag:example.org,2003:3.2397", + "attributes": {} + }, + "title": { + "content": "Atom draft-07 snapshot", + "attributes": {} + }, + "updated": { + "content": "2005-07-31T12:29:29Z", + "attributes": {} + }, + "author": [ + { + "content": { + "name": { + "content": "John Doe", + "attributes": {} + }, + "uri": { + "content": "http://example.org/", + "attributes": {} + }, + "email": { + "content": "mail@example.com", + "attributes": {} + } + }, + "attributes": {} + } + ], + "link": [ + { + "content": null, + "attributes": { + "rel": "alternate", + "type": "text/html", + "href": "http://example.org/2005/04/02/atom" + } + }, + { + "content": null, + "attributes": { + "rel": "enclosure", + "type": "audio/mpeg", + "length": "1337", + "href": "http://example.org/audio/ph34r_my_podcast.mp3" + } + } + ], + "content": { + "content": "[Update: The Atom draft is finished.]", + "attributes": { + "type": "xhtml", + "xml:lang": "en", + "xml:base": "http://diveintomark.org/" + } + }, + "summary": null, + "category": [], + "contributor": [ + { + "content": { + "name": { + "content": "John Doe", + "attributes": {} + }, + "uri": null, + "email": null + }, + "attributes": {} + } + ], + "rights": null, + "published": { + "content": "2003-12-13T08:29:29-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + } + ], + "category": [], + "contributor": [], + "generator": { + "content": "Example Toolkit", + "attributes": { + "uri": "http://www.example.com/", + "version": "1.0" + } + }, + "icon": null, + "logo": null, + "rights": { + "content": "Copyright (c) 2003, John Doe", + "attributes": {} + }, + "subtitle": { + "content": "A <em>lot</em> of effort\n went into making this effortless", + "attributes": { + "type": "html" + } + } + }, + "attributes": {} + } +} diff --git a/tests/samples/generic_atom_feed/data.xml b/tests/samples/atom/generic_atom_feed/data.xml similarity index 100% rename from tests/samples/generic_atom_feed/data.xml rename to tests/samples/atom/generic_atom_feed/data.xml diff --git a/tests/samples/atom/generic_atom_feed/result.json b/tests/samples/atom/generic_atom_feed/result.json new file mode 100644 index 0000000..8640637 --- /dev/null +++ b/tests/samples/atom/generic_atom_feed/result.json @@ -0,0 +1,253 @@ +{ + "@version": null, + "feed": { + "content": { + "id": { + "content": "http://dev.fyicenter.com/atom_xml.php", + "attributes": {} + }, + "title": { + "content": "FYI Center for Software Developers", + "attributes": {} + }, + "updated": { + "content": "2017-09-22T03:58:52+02:00", + "attributes": {} + }, + "author": [ + { + "content": { + "name": { + "content": "FYIcenter.com", + "attributes": {} + }, + "uri": null, + "email": null + }, + "attributes": {} + } + ], + "link": [ + { + "content": null, + "attributes": { + "rel": "self", + "href": "http://dev.fyicenter.com/atom_xml.php" + } + } + ], + "entry": [ + { + "content": { + "id": { + "content": "http://dev.fyicenter.com/1000702_Use_Developer_Portal_Internally.html", + "attributes": {} + }, + "title": { + "content": "Use Developer Portal Internally", + "attributes": {} + }, + "updated": { + "content": "2017-09-20T13:29:08+02:00", + "attributes": {} + }, + "author": [ + { + "content": { + "name": { + "content": "FYIcenter.com", + "attributes": {} + }, + "uri": null, + "email": null + }, + "attributes": {} + } + ], + "link": [ + { + "content": null, + "attributes": { + "rel": "alternate", + "href": "http://dev.fyicenter.com/1000702_Use_Developer_Portal_Internally.ht ml" + } + } + ], + "content": null, + "summary": { + "content": "<img align='left' width='64' height='64' \nsrc='http://dev.fyicenter.com/Azure-API/_icon_Azure-API.png' />How to \nuse the Developer Portal internally by you as the publisher? Normally, \nthe Developer Portal of an Azure API Management Service is used by \nclient developers. But as a publisher, you can also use the Developer \nPortal to test API operations internally. You can follow this tutorial \nto access the ... - Rank: 120; Updated: 2017-09-20 13:29:06 -> <a \nhref='http://dev.fyicenter.com/1000702_Use_Developer_Portal_Internally.ht\nml'>Source</a>", + "attributes": { + "type": "html" + } + }, + "category": [ + { + "content": null, + "attributes": { + "term": "Microsoft" + } + } + ], + "contributor": [], + "rights": null, + "published": null, + "source": null + }, + "attributes": {} + }, + { + "content": { + "id": { + "content": "http://dev.fyicenter.com/1000701_Using_Azure_API_Management_Developer\n_Portal.html", + "attributes": {} + }, + "title": { + "content": "Using Azure API Management Developer Portal", + "attributes": {} + }, + "updated": { + "content": "2017-09-20T13:29:07+02:00", + "attributes": {} + }, + "author": [ + { + "content": { + "name": { + "content": "FYIcenter.com", + "attributes": {} + }, + "uri": null, + "email": null + }, + "attributes": {} + } + ], + "link": [ + { + "content": null, + "attributes": { + "rel": "alternate", + "href": "http://dev.fyicenter.com/1000701_Using_Azure_API_Management_Develop er_Portal.html" + } + } + ], + "content": null, + "summary": { + "content": "<img align='left' width='64' height='64' \nsrc='http://dev.fyicenter.com/Azure-API/_icon_Azure-API.png' />Where to \nfind tutorials on Using Azure API Management Developer Portal? Here is \na list of tutorials to answer many frequently asked questions compiled \nby FYIcenter.com team on Using Azure API Management Developer Portal: \nUse Developer Portal Internally What Can I See on Developer Portal What \nI You T... - Rank: 120; Updated: 2017-09-20 13:29:06 -> <a \nhref='http://dev.fyicenter.com/1000701_Using_Azure_API_Management_Develop\ner_Portal.html'>Source</a>", + "attributes": { + "type": "html" + } + }, + "category": [ + { + "content": null, + "attributes": { + "term": "Microsoft" + } + } + ], + "contributor": [], + "rights": null, + "published": null, + "source": null + }, + "attributes": {} + }, + { + "content": { + "id": { + "content": "http://dev.fyicenter.com/1000700_Add_API_to_API_Products.html", + "attributes": {} + }, + "title": { + "content": "Add API to API Products", + "attributes": {} + }, + "updated": { + "content": "2017-09-20T13:29:06+02:00", + "attributes": {} + }, + "author": [ + { + "content": { + "name": { + "content": "FYIcenter.com", + "attributes": {} + }, + "uri": null, + "email": null + }, + "attributes": {} + } + ], + "link": [ + { + "content": null, + "attributes": { + "rel": "alternate", + "href": "http://dev.fyicenter.com/1000700_Add_API_to_API_Products.html" + } + } + ], + "content": null, + "summary": { + "content": "<img align='left' width='64' height='64' \nsrc='http://dev.fyicenter.com/Azure-API/_icon_Azure-API.png' />How to \nadd an API to an API product for internal testing on the Publisher \nPortal of an Azure API Management Service? You can follow this tutorial \nto add an API to an API product on the Publisher Portal of an Azure API \nManagement Service. 1. Click API from the left menu on the Publisher \nPortal. You s... - Rank: 119; Updated: 2017-09-20 13:29:06 -> <a \nhref='http://dev.fyicenter.com/1000700_Add_API_to_API_Products.html'>Sour\nce</a>", + "attributes": { + "type": "html" + } + }, + "category": [ + { + "content": null, + "attributes": { + "term": "Microsoft" + } + } + ], + "contributor": [], + "rights": null, + "published": null, + "source": null + }, + "attributes": {} + } + ], + "category": [ + { + "content": null, + "attributes": { + "term": "Programming" + } + }, + { + "content": null, + "attributes": { + "term": "Computer" + } + }, + { + "content": null, + "attributes": { + "term": "Developer" + } + } + ], + "contributor": [], + "generator": null, + "icon": null, + "logo": null, + "rights": { + "content": "Copyright (c) 2017 FYIcenter.com", + "attributes": {} + }, + "subtitle": { + "content": "FYI (For Your Information) Center for Software Developers with \nlarge collection of FAQs, tutorials and tips codes for application and \nwWeb developers on Java, .NET, C, PHP, JavaScript, XML, HTML, CSS, RSS, \nMySQL and Oracle - dev.fyicenter.com.", + "attributes": {} + }, + "@xmlns": "http://www.w3.org/2005/Atom" + }, + "attributes": { + "xmlns": "http://www.w3.org/2005/Atom" + } + } +} diff --git a/tests/samples/atom/result.pkl b/tests/samples/atom/result.pkl deleted file mode 100644 index 11a703b4fe362287f0541c9e67386601490d3832..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1510 zcmbVM&ubhv6n5iT?|N}*AgP;@n2JDQOOl<9H^F2HB_Ykl0UwG>55X9D_w7hnBMtfy z+krqXNiMoY{mV*E{bSlEY1ZD9J#}E1dD7GS^?je-7k~WSSxe8mF!0?spbb|t9Qa}m zFbipMs-y*Jvw=UFg9%5`WJPr7PpO4Uwab1h`fGmTtkPfj6_VDFkNMudVv_u#AoxDD z*&U<43(l`Q&9Q(E)Rc>G;MfgZrG7*6idlm@n~ggW+^}39D|~N;1z3*3mwrEo7ya&^ zTVMBfcCf#|Zu?$zd8;IZ&(YkB<_v_TXz%}n&;GOTzgl~>_9^@tKH&Qi-;~^M80GX7 zFn5<a%f9T3T~Cu(YAGFJ-{$2T%-UvBmT*q%M!-<%s?1^8cJ{a)8-2FepQ<`<FB<O% zE>Ghi2g&^)Mj!Zf4I=nWDlBM;?FV1jiv~ZN7S3(SY>nFm(=ZSI?urt2N2AB(=utV^ zEvdzn&aH?Jaw?Q@`YL*#x*1nx-9Q!mh5%VvhIt;2$9tFQ9;`^0k2P%kNFUc1$Bmjz zsj<Vl8DnNhJVGFUeEnM!v7C@Y2IM)`MP{1LE#ZdDxlEW3kJ5Msi+(%jx!2x#Ehd6W ztm_&`WJ0u0sKMHAwFS-i8{iV7Qhl0naL#v)t8w81!M(;4<FSz2mR6GmB8b?19M^a< z9`25x?6sWeSZ>9F&f8bDJ{av0BWYupvaM^^rLNlGYzK+?SNDglmY9UyeTA%B!Mm0L ztdA{k9U-(8NYDtEZdLTn%S<YAsFXP6IUjN(D#(A=c71ksHe9rV<SN6J%>DYq;Rpfn z1xL~11PY-l6%N>~L<WE7g=#L6?#N*J6Ny8(PcD_4wb3)~WO)=XpOdFh|16Y!TH=?e zIf40HY1=1D&l%n$Bw|5oddg)*EJKX5qX1*l<Q5PY31Q>dJsiZ7qyHg<UKGL-Ia*<? z)aEKB6b1wesg>AG+BAaWt%p}-QqMi)+9mz(te}6qOos?u)EThf^K`dYlPr^lz0OXZ d!$h+<>qYt^Dm>sgj+4uO`2?5uR_2cz{tHNiFuMQ% diff --git a/tests/samples/generic_atom_feed/result.pkl b/tests/samples/generic_atom_feed/result.pkl deleted file mode 100644 index e1cd354fb7f01fcda3c8a9c8a98ccc9c6d641458..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4019 zcmdT{U2oeq6b)LZNwgKi(5?ga)w=<MQ_PX=v}+N!NtbyGlC|;DxJBEC!eAuY6hes_ zNyT%40(;r^qPJ@QVSj6XV$ZvzWG7CY#KpP-GhkbysQW>lbMECYr9b|BZ^nLJWQ*zC z0hKBcQPxUJT}u7T4tgTeG}8GEUdVu|>{+&m4_V2~a6F=EPZZ;JxRp9Y<IKH8<K!1v zTGCX8S-P+tcv2|Q)tO32`mV8UX9;O+rWa(;V_IPV3vsf?1H~|kR(e5+M0ymj=18Jh zK6&K76p@gc@K`Cmxbbw$!yu`2JrQQ?;)^Uv&nGg#cV}rx0-m)JJ4(`79z^@bZ62~2 zT};nQ$}vvD(L{uKVdzZKZkBE^t>Z?u>eB<aI|vS)RO_``?N)8M^GMOocl3aA5mVXO z5mFQ0*)p+2g!6&RG=w}$62eNDh)Tkc$U*M>$>O=0nL9z)1Hyw|v|8r0tIN<2e9czN zH*b`IQL{4nwWFlBT0SwJ_2-GCOY1vZ)lPs5+!@`wanvhAb!}7hK?`saML{zPqo5%V z8iG;Kus48#NPs9ibGRw;F0_OUO<EOjCfX2PAQ4#KfU&?fiAaxzxHr(I@&FeQ0tFa? zZ+ti&56mN6-F*F&3*RQ%_$4s%TCuTsXqRv$%*)5(cZvisI|QD#j?Hcmjj&cn@^axR zW@RjO1?Ofx7)GNLjW8x0QxZTtkE%Rv*L4AwV3$Pu4X7{IZo{M4M{xKJSgzG?E!9?* zmTOSI-dJ8~)NaDk8r&h^7~Gt)P1D2133+*~Z7Fc4O4cl@kx_%#qEjQNRFa+xEMJiW z-=Bz#>0P4DoIA5R(0s_Qs|fK|(Q*j!;+?r9U!8)J^Jb}ZfE-N^FJER&XYQ|s%NIYL zNBzmzN;+?*JQoqoUYgqlbL&&C3EtnE*@jQ<&HSG2X20V1H~iZ0aw-p%fH_CGJXv$t z`_D3Vsl_h0*at0krFg+u_{f@>s37dK7P~ss4fgp_FQ+KR?iu?~N5u%DUe3=Ba-?&} zk)e>lNJ&sWM(V+?*;t?4c$=#6m{Ey*L*3{Gkw4;!0ug~tWHQ;0i_2ZuG<J|}4gpkn z4X6}D;vwCTc#-N;2_cCF(3SM31ciYQPzv@@EAUR`WJZ0D1CB!u>c^PuBlk)8nqf7- zIRo7Am=O(4l#DG@Fly`{&n=(gY*}%6_#djqG|+Lzp>w8!uxd_#HvwBJouUBdu(f1m z*K-G7y#`uxnzLK>O?G>z8FN-M*4Q`yOJP_&Ll|nEb>FvwpoRV0k-|?rttM#zyiE-l zb`He%ZFR@GZV77(ZJbAm6;+NZtu4bSa)vFYP1q^AItjy4eRiL4cZynlT0F0~E}Den zo}EMa9kwVg)Lm%uz}vSOcBKey8m2u1>}8S@^U50xHLd~ml{eTqoQ{&5?v6)K>==f8 z|CeYSqxOdgT)R?;3e)rrfqMdtFHA>cY6^RP-e?(j!^-{+n(s*xh6pfhfviT1O_*<D zjld&_ocMHMB;WX$n#Leq=+Ve@i$dagp=7#e^Up<3WG!|nmoI3Fcwo;du;^X04d8hB zDY~F}mFyjDp8m!f1&bR<4s1OoY(>bE&^D&mplOdRmN{)a9Q6rS$We3(&jFf&!-?!s z@UUN{o+*K*aBi$WP?aO)lbFgJ#0sbRR9S;0juF7t5povGW&bhVJJM(y2>(D1NCn*1 z_YW)3tiaCZPQ{M3JsHHh0#CN@S77s@`E9n_71%XD&i0`F;J&r_qa+@u*dqJKPe*RC z^>41zDk;kGJXRdbzC2M&aTmkR&mnhX7TZ%-QQxL~V+_|{n7uoFlO{p@%K65PsUV)t hVxW=A(vORD`u)D!zwQdzt1j1SUsu;L8ANH~{sUMHg1G<y diff --git a/tests/samples/github-49/result.pkl b/tests/samples/github-49/result.pkl deleted file mode 100644 index f4dc84cae9addd57b0335abdeb30f2a17072a869..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26979 zcmd6w>u((Ab;h08a$@%uq(Cp|hinSOaS%y6vzM7&ra+svXq&bX3N&e*52>)bT#Ae- zk|CFNT%Z9hN3k7kEL*1Q!Z%5_(-vqGP?Ts>5+xM|3iRVF`XA)K=(ql!^S(2C<47xU zeX(F^cdqYy&Uwyx&N=VQZanbHV+;4$fB&@kX!kQuv{%;_mX|i|OKW+l(_ZRqcJD78 z4K}+EH#(iwh30yvy~h6a?q^!_jisgbq7I(FUuPUz=(JC+ZGLz2(QMO$jrGp_@~S(g z`+LcY$qoK&6_Sg|R_|1DCE4Zc&15^-+MGY6t3KRnblNADR~NX;cl~J(bM&p`W^bc+ z${p3{ezvuI@+5;>+dS5NxV_X`TwYsW?VoZ?7kjjO|N83UX7`WgJDruanPPD*IJ(+= z{OIa>@rmeoXL;rL%0g?cc(Oga&?qjPJW(tc!)mcyiNm;FE=AQi3ajO#D@!LfyAO1p zT4`@~A6husIMFWJrUw_>OD8(>o89|1*Vh}}2TrUn%=+;bSJ&3sKUi5_?Qp*q_nTSM zmzgMvs`XN-R&T`hN?dJ5jZ!6y<H#n}{k4U)<16jela1qx3rml0b`Q-p7T4OFYwO+5 zuB<nY&mtFp{V)9O`qJS-P%r%D`ck13lq!X=GE*tdl*7VTgK`jfQ9ih~yuR9MBcVf` zh0bDn_EPe1$xiRN-qXo7q<^*0`%&*1RB;J4Tu*j;Pxnq0l3TqEzTRR7U)ji}SCVaQ zEO5^IN0RrY!&}J~8r(sHyS=l8-qQv2xWh$ma_wFA?yxu2>mg05uhKtAb@JqsPkJF0 zO-RL{UJRn5W>vhcypZ{?%(mBBs|za~Z?g0EKeqa*`|eB5Ybw2GG|1lb8ofrCTr;CH zle^B)_$r%vn(^)wl0BJY?^$N0X}rP2uJ^i}cvAv8#l+n#B^7Lyoli4kT|n}<VHe%z z(-rP&r<zq?O$B+G3v6+Zy=3QTAvveBB+Q*;&tHX)?{g6pQs7KPJ1$5Dc{91;2D8Qf zJtme7M`ztkF87{cIO$FI(;jz!+fQbvw^1+&o#kS?$@Rm9-m{u;q4!h%K@OU=yAk7Y zg4)7LIMk#hgW-r9*vJdc;MNVnDfH)m-9Al#`T5W11AYFtkIny%-+gbO4vSHof;tT1 zN*KlA9YH<+MKC@8`)®LAD}OW|B<x7)akcKr@;o_F4{yDy5%;Y_7Eh~oLf$L5c? zam**@3cb_l`5J$AlB=fkJz4c@$%|5vv(v5Q@}OyCCWWyk?_uVs-AwEX-(~jC@s+C; zw2KdXaq658Q?*PC)?8^I%*ZD3SeOFT55;*Jd<x@#j@w_y_RX5H#;zafwwd2G4G+9B z#9mjJ-Ns^V{CxIf@16PJ!Y?;A0Wm_5dAbV<nS8wIY!>sz8yxod_jZn_lKXRJznfeF z$Txat9GoO_g3lQ{(GZ*oT`tB0DA~;*q$z;)<UJcMQ%i@8a1C@wdDn#h0=G9;B9$2O zPzu8r1kI~(YO0V?d>!ip=X1$h4EcG<hcl6vQ(+?)K3`A$@10^|RIHa$_>@c4Agq_7 zqu*O;_k-fYE-1QfX;d5nP&ghBms<<%&Qlf<YwcRoMa1S@H7wPe?b*gbAXJKH!XO7i z-y=52RIZ_tZRc*yhi{vrz`z^M)Y!QrBa+qN?s}-Hfbq!uZrKJxO7Dz(u$k*7hhdb3 z<bqD;pxtD56hv_<@qvg3JD6K-x0l*au350l6-9=h?k-Ls+3$Xb3t<U!?Y+QA&YE!# zPE;XfbarNASl2#tLp%t>MIwo&ce(%|L=S$9G)`maPjd)(QhXI=_pn_Y%S8^|0D9(6 zF4_TS&DL)mb@I5a4WH)(-NW#iGKVSHaHSYWsSTIv)k+jcmAkd!(wGgeEH5r}Adt?8 z=W27#h|8^3IjYPB)!KoKxH40%=NR$hGJ%)OGM$sVDLXY+^%~K51Kr5JQJ@^2%-2Yq zeeM0E@2(1{YD=L6fm;~Y5pX)rhfV0DUiY4?I8PRREB>$iDU3|aPX?c;!CqyA=0jzA zZYtt(<~#=`!yI5|W0M&-+shIUV8{0=RuDMt2B76!?<~<pfVU$G$;<XZ91Yj;aawb! zSj((AtOh|?i%QeB=2p;f)?BH~mgmatx!LxCtT~tohjNnvR?{4o?3iak(+cm3hnQ*s zQ{TqOPxAql$r@0Hb5-cdvJVH2_su`cHheJb^O=cUah?b=BYy15qB4+q5e>ZKduE6V z3l=(Xi{cOOh&nKKvIM?9%JCPIcfQh}k<TX-NNi><B}nI{qybKnH!-Zz!$K6v#KEz$ zP#i<SQy{#<;6}$~o?+(}QNj`<gv-%}E25zRUqd!Zv=3&Ot3u%hXGkyH_#WZnXzlMN z|6<7&h&^u@6LVrP$+mYsS$gm}m)U&?5u#R%GL{omqf!`^YSYG4T=EejsLfUzrMbCw z<sg^}OEW=Urtm{}jM7qaO0y3D@-hkq665eHl;Vm}VJt$t_c_E1x**p|H7u~mkxJiN zM1Pn~A^BJ8I6ve6*Ns)YsWJijx@w1<O6h7JZ-2NC^+}`WXue;|6H^~|XN0>q%^z$j za5>8*g|N8_cU?1hD{hFe?J#a^HF-ZxjV-&DDBQlG`So5fX%Ifx53H0kfNcc9HK12Y zI*6EL14>7b6e$2^t78mIeA)(BVwcz9jml6g7F}Vt!zCwrmPjntIgCwb6wLY|Pr9<v zP3Kb>Jzyv#BXTB}hcL-)AO(3B5(l{pzGQkQu-RV5!oS2hyN|Aqv><d=;}D3sAJBIu zz_IA~8YfUAaTg?6;qX1P70ur!EGcr7L+Qst?BZfAE!dUnVKoYC)p**l4J(lkh0SWv zthDMu=^(HT$}^>M4zXL8RpYkJvK;x4FGsDZQ&zM<RTvC7b?K^82Ij(9qRxUuKL$v( z9Ekn!Kh-Jah-ewJahVR+@Lo2{_9u#tc~a@4^f!glx9#rSZS3|hCA&v}*6tAoi?ylZ zc2$s(0J5QBt-cZ|>$UoTjU8BWNi$=spi%l@a%b>ZoQ-hC1a=WvD3S_B3Yqc<crvb^ ziE=2=Z3*Iqudl6<r&4#FE&L6|<u!Aj{KcO$jMS5CyPt+B0lUPVf|?V2THaGuV|UCU zjxG}3xjf#__{VGFxw-zt_3yY?ucUcW5CbNv)Ke^#RcoG9#z8$QS7zfVzIP;*$&Y5j zN)D2K^v%X<tD&6reQDU46*gnZyXpLN;e-E3E}%2p`2i{HHIBX|#gWKkl%igqM^Pz5 zz4panN9+Waf=W}aHC{wd9b-QXPF_aKlT;sFXN9hp8RYx2R4jgQs+h7X<tFbkbB%qM zBX7XcnBGS3$BG$lHjXwbWV(8`O%tq|9Rrsg`3um*3B(nNMTKfuw}m~4@FlK%mE$%( zpn@#qi9xDvj@NRmLBxQJUfAo4{DbR+uh+SnZbm)EhEIew*QLNMU%<voF7K1T8OK1i ztv|<9r<&R`Tuto@?$6moDyL4JxKE$M!l96>@}tZ|y(zDAJ6&W*6Txd(K~OA!5rSsy zVCQh*m#6-d3`{}tdU9j`IQYroxaDHC=FwfMg_UZ(S}RXESjMvr&vC=JGCLR4%GCpK zTp&)c%&UU@1k;n2yQU-z2P3n%VBkzS(>75-g$#qX6z2?AN1}6u5VeSm+BrkN1SgZ3 zS=^+~oEe0-6FfgO%1lD_HG)do{^$e!ZuoAl$og3Cfb}T&l46xQMxG6RNHYV(4)o;` zcNCd)kxLTURqAcSa1gs<c-VlYTJ78-sCB`FLdI%DTxtP|s-PmT5#Ff`8TQQ%vF)wG zcb3=xeEA7n!$pA#0dqFU6^;cvnp0A<PjxjmbHKpU6>wD*k~VQ4QZS=8e`2*T`2-El zFz)>uW+7st=*%s219o&Bg{XaLe?Sja-OqDTojvV}WxZGpvq)U71f^<NpE6c!QM>Jk zZL7`4QXI`S4-$?m)tPdf6OJ1y_x%j3*gzjdkG@uvUcvdiKG>I*^`s5Js-%VBW#mc4 zo~hO;^CXZ#ulqlDKk&33IX-lzF=16wfltz6!u<-1<YaMJKF4b9pbig3ESEdN1|8G) z&E_LOLlI5T+{vMxABio8#d;cdSesC#Us?;NZOfH7^wsxv5R@9tsB(ZLtvpkXW@x4i ztfd&JSI+v{iWLUrxUk95vWhe!U!?>`{t6C(O8bRCm!FAP7$$TCN|FYZ{Olfat%LP` zM42cj%M(K1oc}wyXg=8GDHg|^?=|1fC|p=no~W*H)<f_P*Ui%{0iY^5IwPZPBQx^z zvCuHkFp_|nsNKV@BXD9A)1qz4|0<V=9lB8<N)1ib_dHvkVhZIN^!K*hfJ$OLWmz$f zJBsSHiE=0#|Ij@Tr)4wNCLMihdA+mVY#(LaQ}Ky~;%u$HR9*enH;#P2QVc-T1nt=N zB<mUC*;dOTsUA1mQ9TUW2k2vozOgVV2T9K+FJVKjC}Fk(t2RJg8%PwAE#+oE#|~7= zv{gJ7$ptIhz+UBN;sv{|UdF%R=)7}><{AY=9qmo}B|a*1vKTX70GT*X%N16JxW`T_ zPaC)&boIv~J(>e9o}v3dr=s^Y%v_8{B^<}{*kGK;N{*beS3sn$@aIog-4D7$SwwAE z)gld_g)lzieTrBTM+}G%jz`|=B<N1pmy87=OX8HJOqt-s;9QhJc6chSz))r+lBRW2 z2wYP_NATw>jPQbH!7=ngtaW1-(77XpM>wU_BK?VZDVT*dRYEP1BdldZ;POmkK$gr4 zhM?O@Wa{_e(U5{BW|5IeW_Iu~k~umHX~T|MJC(EY)Ejnjv*MQ_l|vc}aj6y@U=2li zCalkt^VU%OUGf%syd?cva*pa0&jcu>k8NR0RwQ?|5^aUU$+VT4d-4J=DI^2EvGJzC zBq1+%+I;|iLjZio43Fh;X5@<5Sc)?^6>x6!exx1Xj{%O4OoMjieauO90964v(u~uU z8K@TyU|ImTd72%*V2D(@#v%#<9Gu!s*u*aRC6-j^a_JgNY+AXc_@4E`bTcgdV)F0F zi;ASKuEAa#h9bD~LJ-;8IHDVKd0WS6jXCz+Fu!VOIp?OSyB7L-L}_WQM%@Hm_Aee; zXtY@C`?b}^>?6zWV-AaSk@pM9FHE|)j<-n1lus$u@)=rI4f{x%d2vcOOVvt1PoOsK zW?o#5W?h(^18${qr5+wAOa|aBubJ0W4ze#<8o-ZCPF$aw27fDcG9z(Gvu`75sSbrZ z%+{Q=vpVZXJLlM^n|D>f$P>i~2APE2OBhgPPwW5i6A(L)!OFiv6yv#7t|59@FP&7L z0b`q#=QKO@CBu4pkXz_ymc7OC)&=8gTGMR(cf1hu6j*g8L6-~Ri?Zb}_#e3@Ey^7L zjNH;NvrmSk%-GJc<eI{>g<QysVMl!5LiP>wgpS!On1M?%6O;mQwXjysu^+P}0{}`I zCrLOKBX@b7Vd5r;ZC1(MEl_3j!r4ENeR9f-L9tZM2ym&&pRx$*T`d^A!(ywrK38_= z3mc_IqugvB5Pg+=^u->1I9WIrfBT-e<xQ?tXHMFx72E1Ti-+{i90*c*^Xo(EiwbM! z`nSVYY;4v$92n4emS&ZC+{y5Su;R;#Yd)fzg}SCT;X}O<LFtxK-L$x-u;RaNY4M)~ zV}I`nBYaa6+U1d|dkBqKy0W;-0vXW`e`Vmf$x<s-?FgZRJ-w_HlvPylTt<X(5+b3* zOhMI`kGKdMhQN9|sC?YLwy4+-Qc+x~)Z$t+QJy%_vh+A=riJ*R8P7KB)yAwzBmXIr z+bl~j%~WeMVP0`>!|0U=l1zf8nAWFi5xVOlSq-Yd8Xr;xQ$Ly?%6?SjQ!~x7J3cG- zaR!pE&QsckOBGjQ`31#%j+Ik~I2FGAXMZvZwaN3hJSp{Y_SeGMMTHSEH&yS<Vl^jM zO~y$xEW?C1PUfgOr*aUtXpxikfC%j(VuP%oP*q^bzAmE6_|@#b)piMlr36uX0gLP! z`e$@guu_~Cv2w|e!l{dRPJ~vN;TA}TAr1-Nf{^bFihQPIf#{`FP2o!T%*yskpENrG zP#YDGHHB>uKBQ0ciQLc{0X&R!M9E^NWc3Uf$Q%55aeqjDBz4GAG0JFRR4+%>s5b3& zlXSFwO+8FucK})l?5Z<CUK;kT<W-qQs%Y8N2)((fGK%zt$vE%G8K}ip_Nl6paeigV zZ%`q4cN9t!C1H7!?xW~aLQ!^esE$PgHE0-xAE7g6*)W!9X}hQqU&*-m`?BYDR&!8^ zkl-RDuuImbNCez|YF9IVfqzJs(5Qz?ibwS39OZI*EX4FP{tB?<coi&!ZDpyzT%6Pm zQY$w_sV&t~nS_HySNS)6=nVpCqY$K=n=-Vhcbv5)lfBy}PoIP}kU_DURW74)DGsak zsS~%LT6M&&61Uooc4e;KI#5_*4P;(*@!KM9FPjA^@;HZyo_O?8cBc$!sQZUWLc#|+ zjWR^7uGm2TmQKs_jRNGZGNruP_kr@fqjduvvC$bY5iMn4EVyg|+&OaRx$&}!66`h{ z$$Pu?8bho4lU)aX)fX&`0x)~P#RY(D@rQtkoE(g>d}0IFXQQWn#3jjdTu82r7iB4n zY8IRZS+fx0s4{~~BO`k)Xxh_}Z!;>jOB6x5we-dbTp|rzMp1z=B_~D3Y(QM!7Kh;l zy%)eG+8Q2ROt<3!F4Y*tl(phRu*Po`YWP_8xXe?5(D&Fs##mJ@ma>vzwZ_A+rD*DF zH7b!W8P2szVQV&SL<cAt^6+smQ_Z7@{{RqWZT{&ApJbRv_hmzvl`#gkiEX3}DqO5| ztSnhdh4IMzse|p9X)D>sACH1^qNtTO{XQ5U78uWCR#zAu=HmhaRLIQPqL^X|`(#DQ zQ0n36k76wmZfLkRYpcj-`J`>~%{G^5UACxX6-fz3P#2%dVv?#IdSZ77R-|wsYOHbm z62iJJXxLEGX8|as4<t{L>?iq&mr9V96bK-d3VpR{mlUdj?|Y@pt$u(fUP|hF<*v?u zF8OD?YTB+>Ph(g^vT)~X%?HZBcnF<jM`bX)o7^idICW)03%NN5h{C(jhSAsw{^2gW z0)LH!fqTMDo}e<^bpKD0cXUn|Fg7-_)f>Lclll*LxvuZFCb-QXnJ>{=^Lvl^`J-<? zwaKty*PQsFFGfS0u;H+V+T^=8$tsH}cBaRh#1k*>4T|+Fq*Tfw{h}zCUcn)1mpy)> zO0!Xk<7)i?tBJr*WhTgrCMVo7d1Wu-8G^6^EAr38zi1l9eTwl2I=?)116~3xxXzv{ zjteLc8F>6$d8<8~J_?VC>k7HT$T0HQ*A-j*=qx|Z>|91h8Ur*~*b1;M_G$f-Yrr~c zqgt~cJakrtI-@@Pg$vL_9u_9WWpFR9GAN#edk_<{x-=Ko8V~E0r(Il7HuQc05*p4= zSK>OMlKa0YW=1`LB%i^i&)_RK;iKe&L{4#2?#BBImobh7G@)E&!lB3SO@r?Fl|2Po z)jB9dh_FpS(B3l6=_oA{aA!>6k>YB9QB$c>56bm=t$Md1>GqGsM{{A_<w_}{8lesU z9!q#(ZKe|Cti<@o<bsh4%YM<FV;eF*B}C?a6_Kz>YcvW!iZyC9nT46QUj#&2s>cfr z%t(!4;JG=NW%Xtm?z&1}WE3hUjlFAP67H5Onm)dMOZdtr#R~MSGxVGdP7CK`{KhGj zF$f+iPrK$BrhAr&5P-DWUF$-XA6OlZPgtm^m#bCq+zrhdXP5(W@l>@`IU)#+%O;h9 z%dT<-HX6qiL@y9VUla6P&<A%<>nR%7RJ$bQxo)Asj~&Xb{2J%p+1r*OKJKD;T#T~k zxx%1Q3wa6C6k}9n*2k#0+^Pn(IBFayM)3q@?o-V_{MN$BHV+I9Jb=<qVTBsX;}{?q z62}q45xV3NRXS#)TXNtU{4zgTo3}}`z?(|)Wep(7zm7?6-H85pnPT$Ll_#S<wCvH7 z*X0@rApT)8KLtf0py2Anni<2hu%O(-uRkz0XZ8+q<i~-WkjPYIuyPEz6DGn2TV$f< zlS#mu09=yY3OXEusrwgR@L^0248RXhl?-_ye<xxOTe!2NZ6Qu~2K}l5aG~pk58erl zBqTQockjSkIq;`w%SQDMhg{~5mG@Fzk$Ne#G77zy!1)e&NV+06Vow-(N{xfKzh!E8 z9~+CFv~=C!(V&T4;A%viO@s{3q#zz`Zvm2MlD#_y!p|o^H<6PpBM7-Y|JddfLLuE= zUssQ7L0B%8%Xj}=NqOw;8g1S_w|vsSUZY%Y^URpNHLb?$4r);=2<HwG41-#J;qY|w ziaBQ?5PC!2B@Zi+VVYQ7!6jD;fC?CfU2My*)bX*?x9v;)!qR0~in=>`j?o^mcJye^ zLX54;z;DY7T+>yZO8ZMPCWwpqQtE@Z`Dx<ft4u>3HxV|Uh$__CJ6tr%Y#1_De3D$$ zz*8D3a=Y{gkE4s25oQ+xpH|Bdj|MNg2UC)#vXaU0!8Qh>u3E;fLL|Om>)QvlAVs(( zz_#izEzheZEobWehtUvU*FR$K#T(aN-wWHsoDO_4C!x50gq(#gEE=xqh+XSi=t{+4 ziA^nSbIKdG!Fjl=G!|Y<E+lXBh6jztp94Eku{0yduBv7Cwj<QpJe3JauE-pq>`lyH z!jk|v#);I}zucfbqHpd-Yc@;eDD@v2x{lVP=+?r6uH&suoy<(-V!1ClqyFL=jfDw2 z#rcM=4zZbt3c7y1FTCd4@@+ZBC!y6*?!ObRRIXKc&tN?5@@iCXdHP;%m8<Pmt<gBZ zqj2;&qWm=~f0evrcxFjorWCPGrF$|aGX*X9dzH5B>ke!dKG|!PNM6QiPhVpaqkiXZ z8XUQE?)#s4*Wk#zyCvxcXZ37wwT{#1)e6qj;HKn+!Kckk)lADoghd!It-Fh6+$s&= zR21uQ4w;jJJzU_3clrMUfGTh)cOg5~Jc3dI1#{5GYO{Kvg)x>@!vxN0OUN2aVt|&# zh@V^+oVc6s0cs29s)y)3st`2fQ9-ljKu=aP+!jum4C#l)>%6iB{V+OQ{u2Jm#sXhZ z7R5ycPguUHe(tp%S~P=?Py(R7smgrdyZ6i;S;2?*J{8`$mG#=oVaPidYtw$?Rs;=j zlwhvWXv{X}g607#w&6_mHjBVcV=p^sK<Yxa$_X#YBwa92j8|;XV{O<`>ai^vxyT|D zl+TvMy+-+D6YG;IWjDln5l0<O-HO+2a245uf6`6&<4upfXFj+-3e5>9K{Z!OeMl|| zlD~kcDxhGuui5Q)%!K!utPS{iO&60>Kr!1Dw}#=cQBFskz!Gjny>a2-nk?!_IO3zC z=K>jXWs4^gSoqaPmYQoTUo+Ww#xW>KbYg=+R-DX5{m6CZP~Pmj_qHYpQR<D0uABLt z<)!(?3TiQy&E}EUIxrfwiygsWu0y^>;!<$7Vi$^c&k8dPfm0?jQf-GCTtxRTeI+M6 zek6-vYQ;Ex#RIFBtF=m8nfBUf7FGH8Ic81%0~D?RCe`4)CjXL3!O4}p#sbP1XmVM_ z5?yBxunDx*M!<1`y*JU@Ev$oeB`O!=rg<ZmqKI49h`#R$fqCNSBd{g}PWQ<?p)y@a zteTx3-?2^Ud>>hCe1CoUYlD+MLYUfoWYxA0P9J+lYKlHC(h2x*K*2_lSd>)ZHsi@6 z{Mf$vw7b#GewcNYEGf~&NTaM5_+m?SlHD9E-R*MdUnRlDB{2u4>a;wC%s3v1f}|rs z7}uuodZOm~+QJfVqp|l+M&;Nqg^sJGW-Bhm@d0S2UOqe%=DnV11&o|i<pRZ{Y`*|s zHGA~&tW08PQ5iTg5+_Tr^u~%U01Ak8WsIKcm|2=$iHI-4U|G|KDqu9DaNCcIrqURF zk65l?`mlRNup79cA|4<&7DhN6Szv~p<4&~ROyNMWoH;92S#;ssz6qg)B8!nCqUw$) ztyFu-zo+13q@p4r(<M~{RJx#Vak{k7MK4}3BSha&@qoj(3g2w~>GD!zarVn=!p%TM z%LY1;s%#phkW9olh~h90FrZ*XVZ~;zEk*_et*|B4t3u{f%82Mmqg~Nbx1NZT`VA~c ziY$Rm3!cL%38p09fv-Jpvb7?-)BA1TDE1b^L4t_gBKg!H8x^B0KJxbeN)T11{JLW; zt#iCJMQn6{QdT%q0<?L3os$ZW1zrh^@Ldx^Jqu+mSFuE`)-`+2Ua6_XL8MIEweHJ# zdef*DmRJB#O`F1|nTIXLl>gU(V{n~_pXNy;gK77Fq01JA_a7a2wH=H=&jMgcGRJI? z2Ip*u4&ZkX$WR>8oVf<{6kGE%_f{uAfc)e(#)oFlJoK9~ik2A@J~O4L_2K1jk$!{0 z{SrpV3zJK65ZyHl?(h%~t4sYObhT0&9y(X99AGVINZ6Vw<vl{T@%Qb;j%*_BEuc}i z-V8HWq5?YJ!0N5jfKF9oXFHD@*cqZb0)f1@w5@2C5-h81DoHbIa7{u3)!=e4pNjXZ zx%)-3yf<Rz3MzxhV{a>xz3YIhoWw`6Y>?iFDOh8LG$$0RRm4ykLYbSbpY|0>cNLe{ zskEYEp?kC=OZOx*O<1jdISS&ru1gGZ)l(Hk{QK?<8lw_J+9ML<Iu0M`$XBbJ#K%QR zEJ8sAYB(2?msLJ-E45vD#IR}^se+1o5hn+nWx#S#+AUL{l#^9tgl?T-1r`6vJx<8c zxB~y97z)eiT9z^}c73%cf{B@@ltW0OE{w{GShl6-`1S0>3u*rD!dkog*~P}viFJNX z#{a0xr&rfq6ze|JT3&f-b>YN(M?Y@!WV?C%WMi$<Ufn!4|LC#qZ!GfVcyoPWan}7N zjOC5phfeSs`PD{;-`Y9WePDLErC-K5#P8y)&+_5^PRIXFPxsU1z~ASPy|~}q9>VN? zEypqEP_Lfg7jyIjIqA={=ofP4R*pYWDXu-fuw*}&^F(xHarwluwP@|fKhmG7_-f)7 ze6o_uR%{ril-?^|?-pie3jMd>`_VjLzk1_t|M~RI7h^{qdU9cwAKf|DeQ>_*Bs3o# zb0Y0N$n+QZwH}1lY5$;ed|_#2-9*TESB}r~V>pt@L-uX9@f2Uu;XXWipYQ$g1NYr` bhq3rywCH|rjTdti27ijiMBxUte)RtV+fsC9 diff --git a/tests/samples/apology_line/data.xml b/tests/samples/podcast/apology_line/data.xml similarity index 100% rename from tests/samples/apology_line/data.xml rename to tests/samples/podcast/apology_line/data.xml diff --git a/tests/samples/podcast/apology_line/result.json b/tests/samples/podcast/apology_line/result.json new file mode 100644 index 0000000..b667898 --- /dev/null +++ b/tests/samples/podcast/apology_line/result.json @@ -0,0 +1,292 @@ +{ + "@version": { + "content": "2.0", + "attributes": {} + }, + "channel": { + "content": { + "title": { + "content": "The Apology Line", + "attributes": {} + }, + "link": { + "content": "https://wondery.com/shows/the-apology-line/?utm_source=rss", + "attributes": {} + }, + "description": { + "content": "<p>If you could call a number and say you’re sorry, and no one would know…what would you apologize for? For fifteen years, you could call a number in Manhattan and do just that. This is the story of the line, and the man at the other end who became consumed by his own creation. He was known as “Mr. Apology.” As thousands of callers flooded the line, confessing to everything from shoplifting to infidelity, drug dealing to murder, Mr. Apology realized he couldn’t just listen. He had to do something, even if it meant risking everything. From Wondery the makers of Dr. Death and The Shrink Next Door, comes a story about empathy, deception and obsession. Marissa Bridge, who knew Mr. Apology better than anyone, hosts this six episode series.</p><p>All episodes are available now. You can binge the series ad-free on Wondery+ or on Amazon Music with a Prime membership or Amazon Music Unlimited subscription. </p>", + "attributes": {} + }, + "item": [ + { + "content": { + "title": { + "content": "Wondery Presents - Flipping The Bird: Elon vs Twitter", + "attributes": {} + }, + "link": [ + { + "content": "https://wondery.com/shows/the-apology-line/?utm_source=rss", + "attributes": {} + } + ], + "description": { + "content": "<p>When Elon Musk posted a video of himself arriving at Twitter HQ carrying a white sink along with the message “let that sink in!” It marked the end of a dramatic takeover. Musk had gone from Twitter critic to “Chief Twit” in the space of just a few months but his arrival didn’t put an end to questions about his motives. Musk had earned a reputation as a business maverick. From PayPal to Tesla to SpaceX, his name was synonymous with big, earth-shattering ideas. So, what did he want with a social media platform? And was this all really in the name of free speech...or was this all in the name of Elon Musk? </p><p><br></p><p>From Wondery, the makers of WeCrashed and In God We Lust, comes the wild story of how the richest man alive took charge of the world’s “digital public square.”</p><p><br></p><p>Listen to Flipping The Bird: <a href=\"http://Wondery.fm/FTB_TAL\" rel=\"noopener noreferrer\" target=\"_blank\">Wondery.fm/FTB_TAL</a></p><p>See Privacy Policy at <a href=\"https://art19.com/privacy\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy</a> and California Privacy Notice at <a href=\"https://art19.com/privacy#do-not-sell-my-info\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy#do-not-sell-my-info</a>.</p>", + "attributes": {} + }, + "author": null, + "category": [], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://dts.podtrac.com/redirect.mp3/chrt.fm/track/9EE2G/pdst.fm/e/rss.art19.com/episodes/7bfce2e9-7889-480a-afde-46e810e82b1a.mp3?rss_browser=BAhJIhRweXRob24tcmVxdWVzdHMGOgZFVA%3D%3D--ac965bdf6559f894a935511702ea4ac963845aca", + "type": "audio/mpeg", + "length": "4824502" + } + } + ], + "guid": { + "content": "gid://art19-episode-locator/V0/tdroPC934g1_yKpnqnfmA67RAho9P0W6PUiIY-tBw3U", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2023-05-01T08:00:00", + "attributes": {} + }, + "source": null, + "itunes:title": { + "content": "Wondery Presents - Flipping The Bird: Elon vs Twitter", + "attributes": {} + }, + "itunes:author": null, + "itunes:subtitle": null, + "itunes:summary": { + "content": "When Elon Musk posted a video of himself arriving at Twitter HQ carrying a white sink along with the message “let that sink in!” It marked the end of a dramatic takeover. Musk had gone from Twitter critic to “Chief Twit” in the space of just a few months but his arrival didn’t put an end to questions about his motives. Musk had earned a reputation as a business maverick. From PayPal to Tesla to SpaceX, his name was synonymous with big, earth-shattering ideas. So, what did he want with a social media platform? And was this all really in the name of free speech...or was this all in the name of Elon Musk? \n\n\n\n\nFrom Wondery, the makers of WeCrashed and In God We Lust, comes the wild story of how the richest man alive took charge of the world’s “digital public square.”\n\n\n\n\nListen to Flipping The Bird: Wondery.fm/FTB_TAL\n\nSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.", + "attributes": {} + }, + "itunes:duration": { + "content": "00:05:01", + "attributes": {} + }, + "itunes:episode": null, + "itunes:season": null, + "itunes:episodeType": { + "content": "trailer", + "attributes": {} + }, + "itunes:explicit": { + "content": "yes", + "attributes": {} + }, + "itunes:image": { + "content": null, + "attributes": { + "href": "https://content.production.cdn.art19.com/images/be/e1/82/c2/bee182c2-14b7-491b-b877-272ab6754025/bd4ab6d08d7b723678a682b6e399d26523245b3ba83f61617b9b28396aba1092b101cd86707576ec021b77e143b447463342b352f8825265b15310c989b6cb93.jpeg" + } + }, + "itunes:keywords": { + "content": "Serial killer,TRUE CRIME,Society,This American Life,MURDER,Apology,Apology Line,Binge Worthy Documentary,New York City,Binge-worthy true crime,exhibit c", + "attributes": {} + }, + "itunes:block": null, + "content:encoded": "<p>When Elon Musk posted a video of himself arriving at Twitter HQ carrying a white sink along with the message “let that sink in!” It marked the end of a dramatic takeover. Musk had gone from Twitter critic to “Chief Twit” in the space of just a few months but his arrival didn’t put an end to questions about his motives. Musk had earned a reputation as a business maverick. From PayPal to Tesla to SpaceX, his name was synonymous with big, earth-shattering ideas. So, what did he want with a social media platform? And was this all really in the name of free speech...or was this all in the name of Elon Musk? </p><p><br></p><p>From Wondery, the makers of WeCrashed and In God We Lust, comes the wild story of how the richest man alive took charge of the world’s “digital public square.”</p><p><br></p><p>Listen to Flipping The Bird: <a href=\"http://Wondery.fm/FTB_TAL\" rel=\"noopener noreferrer\" target=\"_blank\">Wondery.fm/FTB_TAL</a></p><p>See Privacy Policy at <a href=\"https://art19.com/privacy\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy</a> and California Privacy Notice at <a href=\"https://art19.com/privacy#do-not-sell-my-info\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy#do-not-sell-my-info</a>.</p>" + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Introducing: The Apology Line", + "attributes": {} + }, + "link": [ + { + "content": "https://wondery.com/shows/the-apology-line/?utm_source=rss", + "attributes": {} + } + ], + "description": { + "content": "<p>If you could call a number and say you’re sorry, and no one would know…what would you apologize for? For fifteen years, you could call a number in Manhattan and do just that. This is the story of the line, and the man at the other end who became consumed by his own creation. He was known as “Mr. Apology.” As thousands of callers flooded the line, confessing to everything from shoplifting to infidelity, drug dealing to murder, Mr. Apology realized he couldn’t just listen. He had to do something, even if it meant risking everything. From Wondery the makers of Dr Death and The Shrink Next Door, comes a story about empathy, deception and obsession. Marissa Bridge, who knew Mr. Apology better than anyone, hosts this six episode series.</p><p>All episodes are available now. You can binge the series ad-free on Wondery+ or on Amazon Music with a Prime membership or Amazon Music Unlimited subscription.</p><p>See Privacy Policy at <a href=\"https://art19.com/privacy\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy</a> and California Privacy Notice at <a href=\"https://art19.com/privacy#do-not-sell-my-info\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy#do-not-sell-my-info</a>.</p>", + "attributes": {} + }, + "author": null, + "category": [], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://dts.podtrac.com/redirect.mp3/chrt.fm/track/9EE2G/pdst.fm/e/rss.art19.com/episodes/a462e9fa-5e7b-4b0a-b992-d59fa1ca06cd.mp3?rss_browser=BAhJIhRweXRob24tcmVxdWVzdHMGOgZFVA%3D%3D--ac965bdf6559f894a935511702ea4ac963845aca", + "type": "audio/mpeg", + "length": "2320091" + } + } + ], + "guid": { + "content": "gid://art19-episode-locator/V0/2E7Nce-ZiX0Rmo017w7js5BvvKiOIMjWELujxOvJync", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2021-01-05T03:26:59", + "attributes": {} + }, + "source": null, + "itunes:title": { + "content": "Introducing: The Apology Line", + "attributes": {} + }, + "itunes:author": null, + "itunes:subtitle": null, + "itunes:summary": { + "content": "If you could call a number and say you’re sorry, and no one would know…what would you apologize for? For fifteen years, you could call a number in Manhattan and do just that. This is the story of the line, and the man at the other end who became consumed by his own creation. He was known as “Mr. Apology.” As thousands of callers flooded the line, confessing to everything from shoplifting to infidelity, drug dealing to murder, Mr. Apology realized he couldn’t just listen. He had to do something, even if it meant risking everything. From Wondery the makers of Dr Death and The Shrink Next Door, comes a story about empathy, deception and obsession. Marissa Bridge, who knew Mr. Apology better than anyone, hosts this six episode series.\n\nAll episodes are available now. You can binge the series ad-free on Wondery+ or on Amazon Music with a Prime membership or Amazon Music Unlimited subscription.\n\nSee Privacy Policy at https://art19.com/privacy and California Privacy Notice at https://art19.com/privacy#do-not-sell-my-info.", + "attributes": {} + }, + "itunes:duration": { + "content": "00:02:24", + "attributes": {} + }, + "itunes:episode": null, + "itunes:season": null, + "itunes:episodeType": { + "content": "trailer", + "attributes": {} + }, + "itunes:explicit": { + "content": "yes", + "attributes": {} + }, + "itunes:image": { + "content": null, + "attributes": { + "href": "https://content.production.cdn.art19.com/images/be/e1/82/c2/bee182c2-14b7-491b-b877-272ab6754025/bd4ab6d08d7b723678a682b6e399d26523245b3ba83f61617b9b28396aba1092b101cd86707576ec021b77e143b447463342b352f8825265b15310c989b6cb93.jpeg" + } + }, + "itunes:keywords": { + "content": "Exhibit C,New York City,Murder,This American Life,society,serial killer,true crime,Apology Line,Binge Worthy Documentary ,Binge-worthy true crime,Apology", + "attributes": {} + }, + "itunes:block": null, + "content:encoded": "<p>If you could call a number and say you’re sorry, and no one would know…what would you apologize for? For fifteen years, you could call a number in Manhattan and do just that. This is the story of the line, and the man at the other end who became consumed by his own creation. He was known as “Mr. Apology.” As thousands of callers flooded the line, confessing to everything from shoplifting to infidelity, drug dealing to murder, Mr. Apology realized he couldn’t just listen. He had to do something, even if it meant risking everything. From Wondery the makers of Dr Death and The Shrink Next Door, comes a story about empathy, deception and obsession. Marissa Bridge, who knew Mr. Apology better than anyone, hosts this six episode series.</p><p>All episodes are available now. You can binge the series ad-free on Wondery+ or on Amazon Music with a Prime membership or Amazon Music Unlimited subscription.</p><p>See Privacy Policy at <a href=\"https://art19.com/privacy\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy</a> and California Privacy Notice at <a href=\"https://art19.com/privacy#do-not-sell-my-info\" rel=\"noopener noreferrer\" target=\"_blank\">https://art19.com/privacy#do-not-sell-my-info</a>.</p>" + }, + "attributes": {} + } + ], + "language": { + "content": "en", + "attributes": {} + }, + "copyright": { + "content": "© 2021 Wondery, Inc. All rights reserved", + "attributes": {} + }, + "managingEditor": { + "content": "iwonder@wondery.com (Wondery)", + "attributes": {} + }, + "webMaster": null, + "pubDate": null, + "lastBuildDate": null, + "category": [], + "generator": { + "content": "ART19", + "attributes": {} + }, + "docs": null, + "cloud": null, + "ttl": null, + "image": { + "content": { + "url": { + "content": "https://content.production.cdn.art19.com/images/be/e1/82/c2/bee182c2-14b7-491b-b877-272ab6754025/bd4ab6d08d7b723678a682b6e399d26523245b3ba83f61617b9b28396aba1092b101cd86707576ec021b77e143b447463342b352f8825265b15310c989b6cb93.jpeg", + "attributes": {} + }, + "title": { + "content": "The Apology Line", + "attributes": {} + }, + "link": { + "content": "https://wondery.com/shows/the-apology-line/?utm_source=rss", + "attributes": {} + }, + "width": null, + "height": null, + "description": null + }, + "attributes": {} + }, + "rating": null, + "textInput": null, + "skipHours": null, + "skipDays": null, + "itunes:author": { + "content": "Wondery", + "attributes": {} + }, + "itunes:type": { + "content": "serial", + "attributes": {} + }, + "itunes:title": null, + "itunes:subtitle": null, + "itunes:summary": { + "content": "<p>If you could call a number and say you’re sorry, and no one would know…what would you apologize for? For fifteen years, you could call a number in Manhattan and do just that. This is the story of the line, and the man at the other end who became consumed by his own creation. He was known as “Mr. Apology.” As thousands of callers flooded the line, confessing to everything from shoplifting to infidelity, drug dealing to murder, Mr. Apology realized he couldn’t just listen. He had to do something, even if it meant risking everything. From Wondery the makers of Dr. Death and The Shrink Next Door, comes a story about empathy, deception and obsession. Marissa Bridge, who knew Mr. Apology better than anyone, hosts this six episode series.</p><p>All episodes are available now. You can binge the series ad-free on Wondery+ or on Amazon Music with a Prime membership or Amazon Music Unlimited subscription. </p>", + "attributes": {} + }, + "itunes:owner": { + "content": { + "itunes:name": { + "content": "Wondery", + "attributes": {} + }, + "itunes:email": { + "content": "iwonder@wondery.com", + "attributes": {} + } + }, + "attributes": {} + }, + "itunes:image": { + "content": null, + "attributes": { + "href": "https://content.production.cdn.art19.com/images/be/e1/82/c2/bee182c2-14b7-491b-b877-272ab6754025/bd4ab6d08d7b723678a682b6e399d26523245b3ba83f61617b9b28396aba1092b101cd86707576ec021b77e143b447463342b352f8825265b15310c989b6cb93.jpeg" + } + }, + "itunes:category": [ + { + "content": null, + "attributes": { + "text": "True Crime" + } + } + ], + "itunes:explicit": { + "content": "yes", + "attributes": {} + }, + "itunes:keywords": { + "content": "Exhibit C,Binge-worthy true crime,New York City,Binge Worthy Documentary ,Apology Line,Apology,Murder,This American Life,society,true crime,serial killer", + "attributes": {} + }, + "itunes:new-feed-url": null, + "itunes:block": null, + "itunes:complete": null, + "atom:link": { + "@href": "https://rss.art19.com/apology-line", + "@rel": "self", + "@type": "application/rss+xml" + } + }, + "attributes": {} + }, + "@xmlns:itunes": "http://www.itunes.com/dtds/podcast-1.0.dtd", + "@xmlns:atom": "http://www.w3.org/2005/Atom", + "@xmlns:content": "http://purl.org/rss/1.0/modules/content/", + "@xmlns:art19": "https://art19.com/xmlns/rss-extensions/1.0", + "@xmlns:googleplay": "http://www.google.com/schemas/play-podcasts/1.0/" +} diff --git a/tests/samples/rdf/rdf_1_0/data.xml b/tests/samples/rdf/rdf_1_0/data.xml new file mode 100644 index 0000000..2f729f3 --- /dev/null +++ b/tests/samples/rdf/rdf_1_0/data.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<rdf:RDF + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" + xmlns="http://purl.org/rss/1.0/"> + + <channel rdf:about="http://meerkat.example.com/?_fl=rss1.0"> + <title>Meerkat + http://meerkat.example.com + Meerkat: An Open Wire Service + The Example Network + Rael Example (rael@example.com) + Copyright 2000 The Example Network + 2000-01-01T12:00+00:00 + hourly + 2 + 2000-01-01T12:00+00:00 + + + + + + + + + + + + + + + Meerkat Powered! + http://meerkat.example.com/icons/meerkat-powered.jpg + http://meerkat.example.com + + + + XML: A Disruptive Technology + http://c.example.com/rcs_id=1234 + XML is placing increasingly heavy loads on the existing technical infrastructure of the Internet. + Simon St.Example + + + + A Second Item Without A Description + http://c.example.com/rcs_id=1235 + + + + Search Meerkat + Search Meerkat's RSS Database... + s + http://meerkat.example.com/ + + diff --git a/tests/samples/rdf/rdf_1_0/result.json b/tests/samples/rdf/rdf_1_0/result.json new file mode 100644 index 0000000..ecea2d9 --- /dev/null +++ b/tests/samples/rdf/rdf_1_0/result.json @@ -0,0 +1,120 @@ +{ + "channel": { + "content": { + "title": { + "content": "Meerkat", + "attributes": {} + }, + "link": { + "content": "http://meerkat.example.com", + "attributes": {} + }, + "description": { + "content": "Meerkat: An Open Wire Service", + "attributes": {} + }, + "image": { + "content": null, + "attributes": { + "rdf:resource": "http://meerkat.example.com/icons/meerkat-powered.jpg" + } + }, + "textinput": { + "content": null, + "attributes": { + "rdf:resource": "http://meerkat.example.com" + } + }, + "@rdf:about": "http://meerkat.example.com/?_fl=rss1.0", + "dc:publisher": "The Example Network", + "dc:creator": "Rael Example (rael@example.com)", + "dc:rights": "Copyright 2000 The Example Network", + "dc:date": "2000-01-01T12:00+00:00", + "syn:updatePeriod": "hourly", + "syn:updateFrequency": "2", + "syn:updateBase": "2000-01-01T12:00+00:00", + "items": { + "rdf:Seq": { + "rdf:li": [ + { + "@rdf:resource": "http://c.example.com/rcs_id=1234" + }, + { + "@rdf:resource": "http://c.example.com/rcs_id=1235" + } + ] + } + } + }, + "attributes": { + "rdf:about": "http://meerkat.example.com/?_fl=rss1.0" + } + }, + "item": [ + { + "content": { + "title": { + "content": "XML: A Disruptive Technology", + "attributes": {} + }, + "link": { + "content": "http://c.example.com/rcs_id=1234", + "attributes": {} + }, + "description": { + "content": "XML is placing increasingly heavy loads on the existing technical infrastructure of the Internet.", + "attributes": {} + }, + "@rdf:about": "http://c.example.com/rcs_id=1234", + "dc:creator": "Simon St.Example" + }, + "attributes": { + "rdf:about": "http://c.example.com/rcs_id=1234" + } + }, + { + "content": { + "title": { + "content": "A Second Item Without A Description", + "attributes": {} + }, + "link": { + "content": "http://c.example.com/rcs_id=1235", + "attributes": {} + }, + "description": null, + "@rdf:about": "http://c.example.com/rcs_id=1235" + }, + "attributes": { + "rdf:about": "http://c.example.com/rcs_id=1235" + } + } + ], + "image": { + "content": { + "@rdf:about": "http://meerkat.example.com/icons/meerkat-powered.jpg", + "title": "Meerkat Powered!", + "url": "http://meerkat.example.com/icons/meerkat-powered.jpg", + "link": "http://meerkat.example.com" + }, + "attributes": { + "rdf:about": "http://meerkat.example.com/icons/meerkat-powered.jpg" + } + }, + "textinput": { + "content": { + "@rdf:about": "http://meerkat.example.com", + "title": "Search Meerkat", + "description": "Search Meerkat's RSS Database...", + "name": "s", + "link": "http://meerkat.example.com/" + }, + "attributes": { + "rdf:about": "http://meerkat.example.com" + } + }, + "@xmlns:rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "@xmlns:dc": "http://purl.org/dc/elements/1.1/", + "@xmlns:syn": "http://purl.org/rss/1.0/modules/syndication/", + "@xmlns": "http://purl.org/rss/1.0/" +} diff --git a/tests/samples/github-49/data.xml b/tests/samples/rss/github-49/data.xml similarity index 100% rename from tests/samples/github-49/data.xml rename to tests/samples/rss/github-49/data.xml diff --git a/tests/samples/rss/github-49/result.json b/tests/samples/rss/github-49/result.json new file mode 100644 index 0000000..05958b7 --- /dev/null +++ b/tests/samples/rss/github-49/result.json @@ -0,0 +1,3185 @@ +{ + "@version": { + "content": "2.0", + "attributes": {} + }, + "channel": { + "content": { + "title": { + "content": "www.rbc.ru", + "attributes": {} + }, + "link": { + "content": "https://www.rbc.ru", + "attributes": {} + }, + "description": { + "content": "stub description", + "attributes": {} + }, + "item": [ + { + "content": { + "title": { + "content": "Чешский футболист пропустит Евро из-за падения с велосипеда", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/666592289a79475b6a241776", + "attributes": {} + } + ], + "description": { + "content": "Полузащитник Садилек после матча с Мальтой тренировался по индивидуальной программе. Во время поездки на велосипеде он травмировал ногу и пропустит Евро. Тренер заявил, что эта травма ослабила сборную", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/15/347179326576153.png", + "type": "image/png", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:666592289a79475b6a241776", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:42:31+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:42:31", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/666592289a79475b6a241776", + "rbc_news:anons": "Полузащитник Садилек после матча с Мальтой тренировался по индивидуальной программе. Во время поездки на велосипеде он травмировал ногу и пропустит Евро. Тренер заявил, что эта травма ослабила сборную", + "rbc_news:news_id": "666592289a79475b6a241776", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717933351", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:42:33 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "Футбол", + "Евро-2024", + "Чехия" + ], + "rbc_news:full-text": "Полузащитник «Твенте» Михал Садилек не выступит на чемпионате Европы в составе сборной Чехии из-за травмы голени, которую он получил в субботу в результате падения с велосипеда. Об этом сообщила пресс-служба Футбольной ассоциации Чехии.\n\nСадилек 7 июня отыграл товарищеский матч против Мальты (7:1), во втором тайме он надел капитанскую повязку.\n\nГлавный тренер сборной Чехии Иван Гашек заявил, что травма Садилека ослабила команду. Полузащитник был одним из семи игроков сборной, которые участвовали в Евро-2020.\n\n\n\nВсего на счету Садилека 24 матча и один гол за сборную.\n\nНа Евро чехи сыграют в одной группе с Португалией, Турцией и Грузией.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/3/15/347179326576153.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Gabriele Maltinti / Getty Images", + "rbc_news:description": "

Михал Садилек

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/08/06/2024/666431ef9a794771470d8127", + "rbc_news:title": "Мбаппе и Гризманн вошли в состав сборной Франции на Евро-2024", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/82/347178424573820.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Mike Hewitt / Getty Images", + "description": "

Килиан Мбаппе

" + } + }, + { + "@url": "https://www.rbc.ru/sport/08/06/2024/666411bf9a7947155de8d4e2", + "rbc_news:title": "Лидер сборной Польши получил травму в матче с Украиной и пропустит Евро", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/4/03/347178342040034.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Valerio Pennicino / Getty Images", + "description": "

Аркадиуш Милик

" + } + }, + { + "@url": "https://www.rbc.ru/sport/08/06/2024/666406da9a7947016d187487", + "rbc_news:title": "Игроки «Реала» и «Челси» вошли в состав сборной Украины на Евро-2024", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/25/347178314474255.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Ryan Pierse / Getty Images", + "description": "

Михаил Мудрик

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "В хоккейном СКА отреагировали на пожар на домашней арене", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/66658b889a794752eee761e9", + "attributes": {} + } + ], + "description": { + "content": "Пожар произошел в ночь на воскресенье в чаше арены — сгорел видеобортик, пострадали трибуны и покрытие. В СКА заявили, что в межсезонье не занимаются на этом стадионе", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/1/67/347179310741671.png", + "type": "image/png", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:66658b889a794752eee761e9", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:31:45+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:31:45", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/66658b889a794752eee761e9", + "rbc_news:anons": "Пожар произошел в ночь на воскресенье в чаше арены — сгорел видеобортик, пострадали трибуны и покрытие. В СКА заявили, что в межсезонье не занимаются на этом стадионе", + "rbc_news:news_id": "66658b889a794752eee761e9", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717932705", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:31:47 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "Хоккей", + "ХК СКА Санкт-Петербург" + ], + "rbc_news:full-text": "Пожар, произошедший ночью на «СКА Арене» в Санкт-Петербурге, не отразился на хоккейном клубе СКА, стадион не задействован в предсезонной подготовке команды. Об этом сообщила пресс-служба СКА.\n\nПожар в чаше арены произошел в ночь на 9 июня. ТАСС со ссылкой на оперативные службы сообщил, что на арене выгорел электронный видеобортик, оплавились и сгорели трибуны на площади 200 кв. метров, сгорело 100 кв. метров и оплавилось 300 кв. метров пластикового покрытия чаши.\n\nВ СКА сообщили, что после плей-офф у клуба нет договорных отношений с ареной, на которой она проводила домашние матчи во второй половине сезона КХЛ.\n\n«Мы тренируемся в Новогорске и Сочи, домашний турнир Пучкова проводим в Ледовом, поэтому этот пожар никак не влияет на наше расписание», — сообщили в клубе.\n\n\n\nВ СКА назвали пожар «неприятным моментом, так как нет ничего важнее безопасности всех, кто мог находиться на арене».\n\nВ результате возгорания никто не пострадал.\n\nАрена, которая вмещает более 20 тыс. зрителей на хоккейных матчах, была введена в эксплуатацию в конце прошлого года. Гендиректор Дмитрий Сватковский в ноябре оценивал ее стоимость в 60 млрд. рублей.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/1/67/347179310741671.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Александр Демьянчук / ТАСС", + "rbc_news:description": "

«СКА Арена» в Санкт-Петербурге

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/07/06/2024/6662db379a7947a929f85a7f", + "rbc_news:title": "Хельсинки обновили предложение по покупке арены у Тимченко и Ротенберга", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/1/86/347177547708861.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Tomi Hänninen / Newspix24 / Newspix24 / Global Look Press" + } + }, + { + "@url": "https://www.rbc.ru/sport/15/02/2024/65ccfa599a79470d49a9eb6d", + "rbc_news:title": "«Посчитали кошек и собак». Действительно ли СКА установил мировой рекорд", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/74/347079323125745.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Валентина Певцова / ТАСС", + "description": "

«СКА Арена»

" + } + }, + { + "@url": "https://www.rbc.ru/sport/04/03/2024/65e5fcec9a794738ffd036b0", + "rbc_news:title": "РФС разрешил «Спартаку» вернуться на домашний стадион после ЧП с травой" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Парковки по всей Москве сделают бесплатными в День России", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66658e8b9a7947bf5129beda", + "attributes": {} + } + ], + "description": { + "content": "В День России, 12 июня, парковка в Москве станет бесплатной на всех улицах, сообщил мэр Сергей Собянин.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Общество", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/92/347179332501926.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:society:66658e8b9a7947bf5129beda", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:27:10+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:27:10", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66658e8b9a7947bf5129beda", + "rbc_news:anons": "В День России, 12 июня, парковка в Москве станет бесплатной на всех улицах, сообщил мэр Сергей Собянин.", + "rbc_news:news_id": "66658e8b9a7947bf5129beda", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717932430", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:56:05 +0300", + "rbc_news:newsline": "society", + "rbc_news:tag": [ + "Москва", + "Сергей Собянин", + "парковки", + "День России" + ], + "rbc_news:full-text": "В День России, 12 июня, парковка в Москве станет бесплатной на всех улицах, сообщил мэр Сергей Собянин.\n\nОн уточнил, что парковки со шлагбаумом останутся платными и продолжат работать по действующим тарифам.\n\n12 мая — нерабочий праздничный день. В прошлом году День России выпал на понедельник, поэтому жители страны отдыхали сразу три дня. В этом россияне отдохнут в среду.\n\n\n\nПлатные парковки существуют в Москве с 2012 года. Их зону после этого регулярно расширяли. В январе 2024 году появились еще 50 участков, среди них улицы в районах Аэропорт, Северное Измайлово, Савеловский, Зюзино и др. А в феврале зону парковок расширили еще на 31 участок.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/6/92/347179332501926.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Екатерина Кузьмина / РБК" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/664607b59a794728a8232caa", + "rbc_news:title": "Прокуратура опротестовала запрет на парковку электросамокатов в Перми", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/26/347158669327266.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Константин Кокошкин / Global Look Press" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/662cec2e9a794756cb9fb1f4", + "rbc_news:title": "В Люберцах простились с погибшим из-за замечания о парковке", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/00/347142231644002.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Максим Григорьев / ТАСС" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6629e31a9a7947daddcf1305", + "rbc_news:title": "В Москве на майские праздники парковку сделают бесплатной", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/65/347140246903656.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Екатерина Кузьмина / РБК" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "В Белгороде и Курской области объявили ракетную опасность", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66658f8f9a79473cc364f058", + "attributes": {} + } + ], + "description": { + "content": "В Белгороде и Белгородском районе объявлена ракетная опасность, сообщил глава региона Вячеслав Гладков.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/4/76/347179329546764.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:66658f8f9a79473cc364f058", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:24:59+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:24:59", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66658f8f9a79473cc364f058", + "rbc_news:anons": "В Белгороде и Белгородском районе объявлена ракетная опасность, сообщил глава региона Вячеслав Гладков.", + "rbc_news:news_id": "66658f8f9a79473cc364f058", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717932299", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:52:11 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Белгород", + "ракетная опасность", + "Вячеслав Гладков", + "Курская область" + ], + "rbc_news:full-text": "В Белгороде и Белгородском районе объявлена ракетная опасность, сообщил глава региона Вячеслав Гладков. Он призвал жителей спуститься в подвалы и оставаться там до получения сигнала сообщений об отбое ракетной опасности. Сигнал продлился 16 минут, с 14:15 до 14:31 мск.\n\nАналогичный сигнал запустили в Курской области, сообщил врио губернатора Алексей Смирнов. Он призвал укрыться в помещениях со сплошными стенами или спуститься в укрытия.\n\n\n\nБелгородская область и ее административный центр регулярно попадают под атаки. В регионе практически ежедневно запускают сигналы ракетной опасности. Сегодня такой сигнал запускают в третий раз.\n\nВ Курской области также неоднократно запускали сигнал ракетной опасности. В частности, 4 июня его объявили в городе Курчатов, где находится АЭС.\n\nПосле начала военной операции России на Украине приграничные регионы регулярно попадают под атаки дронов и обстрелы. Ранее днем ракетную опасность объявляли в Воронежской области, а Херсонской запускали авиационную опасность.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/4/76/347179329546764.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Александр Рюмин / ТАСС", + "rbc_news:description": "

Белгород

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/6665750c9a794710b7db959e", + "rbc_news:title": "Жителей Белгорода во второй раз за день предупредили о ракетной опасности", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/40/347179255944402.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Александр Рюмин / ТАСС", + "description": "

Белгород

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/66656bfb9a794747ceae4f91", + "rbc_news:title": "Над Белгородской областью сбили ракету «Нептун» и два дрона", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/74/347179234584748.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Николай Гынгазов / ТАСС" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6665564c9a7947d363ac1ebd", + "rbc_news:title": "В Воронежской области вслед за Белгородом объявили ракетную опасность", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/90/347179180733905.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Сергей Бобылев / ТАСС", + "description": "

Воронеж

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Гладков сообщил об атаке двух дронов на село Безымено", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66658c0a9a794744d3f3efde", + "attributes": {} + } + ], + "description": { + "content": "Вооруженные силы Украины (ВСУ) атаковали двумя дронами село Безымено Грайворонского городского округа, никто не пострадал, сообщил губернатор Вячеслав Гладков.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/86/347179315001862.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:66658c0a9a794744d3f3efde", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:20:11+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:20:11", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66658c0a9a794744d3f3efde", + "rbc_news:anons": "Вооруженные силы Украины (ВСУ) атаковали двумя дронами село Безымено Грайворонского городского округа, никто не пострадал, сообщил губернатор Вячеслав Гладков.", + "rbc_news:news_id": "66658c0a9a794744d3f3efde", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717932011", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:50:28 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Белгородская область", + "ВСУ", + "Украина", + "Вячеслав Гладков" + ], + "rbc_news:full-text": "Вооруженные силы Украины (ВСУ) атаковали двумя дронами село Безымено Грайворонского городского округа, никто не пострадал, сообщил губернатор Вячеслав Гладков.\n\nПервый беспилотник сбросил взрывное устройство на автомобиль; пассажиры успели покинуть салон. Второй сбросил взрывчатку на административный объект — там пробило крышу.\n\n\nГрайворонский городской округ расположен в считанных километрах от границы с Украиной. В 2021 году в городе жили 6179 человек. Округ регулярно попадает под обстрелы.\n\n\n\n\nБелгородская область в последние месяцы подвергается интенсивным обстрелам, в регионе периодически объявляют ракетную опасность, сбивают украинские ракеты и дроны. Ранее днем ВСУ атаковали Шебекинский и Новооскольский городские округа, никто не пострадал.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/2/86/347179315001862.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "сервис «Яндекс.Карты»" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/6665750c9a794710b7db959e", + "rbc_news:title": "Жителей Белгорода во второй раз за день предупредили о ракетной опасности", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/40/347179255944402.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Александр Рюмин / ТАСС", + "description": "

Белгород

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/66656bfb9a794747ceae4f91", + "rbc_news:title": "Над Белгородской областью сбили ракету «Нептун» и два дрона", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/74/347179234584748.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Николай Гынгазов / ТАСС" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6665564c9a7947d363ac1ebd", + "rbc_news:title": "В Воронежской области вслед за Белгородом объявили ракетную опасность", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/90/347179180733905.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Сергей Бобылев / ТАСС", + "description": "

Воронеж

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Минздрав Газы сообщил о 274 погибших в ходе операции ЦАХАЛ в Нусейрате", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/6665872f9a794708d5a2ffe4", + "attributes": {} + } + ], + "description": { + "content": "Накануне израильский спецназ провел спецоперацию по освобождению четырех заложников, включая одного россиянина, в лагере беженцев Нусейрат. По данным минздрава анклава, в результате погибли как минимум 274 человека", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/62/347179305621628.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:6665872f9a794708d5a2ffe4", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:12:05+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:12:05", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/6665872f9a794708d5a2ffe4", + "rbc_news:anons": "Накануне израильский спецназ провел спецоперацию по освобождению четырех заложников, включая одного россиянина, в лагере беженцев Нусейрат. По данным минздрава анклава, в результате погибли как минимум 274 человека", + "rbc_news:news_id": "6665872f9a794708d5a2ffe4", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717931525", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:18:40 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Израиль", + "ХАМАС", + "заложники", + "сектор Газа" + ], + "rbc_news:full-text": "По меньшей мере 274 палестинца погибли и 698 получили ранения в результате израильской операции по освобождению четырех заложников, проводившейся в центре лагеря палестинских беженцев Нусейрат в центральной части сектора Газа. Об этом сообщило в своем телеграм-канале министерство здравоохранения анклава, находящегося под контролем ХАМАС.\n\n«Многие пострадавшие по-прежнему находятся под завалами и на дорогах, бригады скорой помощи и гражданской обороны не могут добраться до них», — уточнили в министерстве.\n\nВсего, по данным минздрава, число жертв среди палестинцев с 7 октября превысило 37 тыс., еще свыше 84 тыс. получили ранения.\n\n\n\nИзраильский спецназ провел операцию по освобождению заложников, захваченных боевиками на музыкальном фестивале «Нова», накануне, 8 июня. В результате удалось освободить четырех человек, включая 27-летнего Андрея Козлова, гражданина России и Израиля. Операцию проводили при поддержке армии, разведки и ВВС. Во время рейда погиб один спецназовец.\n\nИсточники The New York Times (NYT) уточняли, что саму операцию согласовали за несколько минут до начала. Израильские военные решили действовать днем, поскольку в ХАМАС предполагали, что подобная операция пройдет ночью. Один заложник находился в первом здании, трое, в том числе Козлов, — в другом, и для их освобождения спецназ вступил в перестрелку. После освобождения из плена россиянин встретился с премьер-министром Израиля Биньямином Нетаньяху и поблагодарил его по-русски: «Спасибо вам».", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/8/62/347179305621628.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Emad Abu Shawiesh / Reuters", + "rbc_news:description": "

Нейсурат, сектор Газа

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/politics/09/06/2024/66650aea9a7947565c7a22f9", + "rbc_news:title": "WSJ узнала об угрозах Египта и Катара лидерам ХАМАС по требованию США", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/1/14/347179010398141.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Ramadan Abed / Reuters" + } + }, + { + "@url": "https://www.rbc.ru/politics/09/06/2024/6664e6f39a794775ba2f384b", + "rbc_news:title": "NYT узнала подробности освобождения захваченного ХАМАС россиянина" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/66649b959a794720085861e8", + "rbc_news:title": "Освобожденный из плена ХАМАС петербуржец встретился с Нетаньяху" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "На греческом острове нашли тело британского телеведущего Мосли", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/666581469a79474b50b4c902", + "attributes": {} + } + ], + "description": { + "content": "Тело ведущего Би-би-си Майкла Мосли, пропавшего во время отдыха на острове Сими в Греции, нашли, сообщает Associated Press со ссылкой на представителя полиции.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/85/347179291561857.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666581469a79474b50b4c902", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T14:03:23+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "14:03:23", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/666581469a79474b50b4c902", + "rbc_news:anons": "Тело ведущего Би-би-си Майкла Мосли, пропавшего во время отдыха на острове Сими в Греции, нашли, сообщает Associated Press со ссылкой на представителя полиции.", + "rbc_news:news_id": "666581469a79474b50b4c902", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717931003", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:07:45 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "смерть", + "Греция", + "Би-би-си", + "журналист" + ], + "rbc_news:full-text": "Тело ведущего Би-би-си Майкла Мосли, пропавшего во время отдыха на острове Сими в Греции, нашли, сообщает Associated Press со ссылкой на представителя полиции. Информацию подтвердил источник Би-би-си.\n\nПо словам мэра Лефтериса Папакалодукаса, тело упало с крутого склона, в одной руке у погибшего была кожаная сумка. По данным Би-би-си, его обнаружили при осмотре береговой линии с помощью камер. Тело вскоре пройдет официальное опознание.\n\n67-летний журналист пропал 5 июня. В поисковой операции участвовали полиция, береговая охрана, волонтеры и другие силы. У Мосли остались жена и четверо детей.\n\n\n\nЖурналист получил медицинское образование, после работал в кадре почти 20 лет:, вел шоу о диете, физических упражнениях и медицине. Мосли знаком британским зрителям по выступлениям в утреннем эфире Би-би-си и на шоу «Один на один». Кроме того, он популяризировал периодическое голодание и низкоуглеводное питание. Мосли также был обозревателем Daily Mail.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/7/85/347179291561857.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Panormitis Chatzigiannakis / Reuters", + "rbc_news:description": "

Обстановка на месте происшествия на острове Сими, Греция

" + }, + "rbc_news:related_links": { + "link": { + "@url": "https://www.rbc.ru/rbcfreenews/666354ac9a79470ba0cd9f5b", + "rbc_news:title": "Британский телеведущий Мосли пропал во время отдыха на греческом острове", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/9/47/347177862568479.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Fernvall Lotte / Aftonbladet / Zuma / Global Look Press", + "description": "

Майкл Мосли

" + } + } + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Marca узнала о планах «Реала» купить самого дорогого немецкого футболиста", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/6665858a9a7947709634d767", + "attributes": {} + } + ], + "description": { + "content": "Газета узнала, что Флориан Вирц договорился провести еще один сезон за «Байер», а потом перейти в «Реал». Леверкузенский клуб ранее заявил, что планирует получить за футболиста, признанного лучшим в бундеслиге, €150 млн", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/94/347179307185947.png", + "type": "image/png", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:6665858a9a7947709634d767", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:59:14+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:59:14", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/6665858a9a7947709634d767", + "rbc_news:anons": "Газета узнала, что Флориан Вирц договорился провести еще один сезон за «Байер», а потом перейти в «Реал». Леверкузенский клуб ранее заявил, что планирует получить за футболиста, признанного лучшим в бундеслиге, €150 млн", + "rbc_news:news_id": "6665858a9a7947709634d767", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717930754", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:16:13 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "Футбол", + "ФК Байер Леверкузен", + "Реал Мадрид" + ], + "rbc_news:full-text": "Полузащитник леверкузенского «Байера» Флориан Вирц стал целью мадридского «Реала» на лето 2025 года, пишет Marca.\n\nПо данным издания, 21-летний футболист договорился с «Байером», что отыграет за него еще один сезон, а с «Реалом» — о переходе в 2025-м.\n\nПри этом контракт футболиста с леверкузенским клубом рассчитан до лета 2027 года, а в «Байере» в апреле заявили, что хотят получить за игрока не меньше €150 млн.\n\nПортал Transfermarkt оценивает стоимость Вирца в €130 млн — он самый дорогой игрок бундеслиги и седьмой по стоимости в мире.\n\n\n\nВирц был признан лучшим игроком бундеслиги в минувшем сезоне, он выиграл с «Байером» чемпионат и Кубок Германии.\n\nВ сборной Германии полузащитник провел 18 матчей и забил один гол.\n\nВирц вошел в состав сборной на Евро-2024, который стартует 14 июня.\n\nСамым дорогим трансфером немецкого игрока в истории является переход Кая Хаверца из «Леверкузена» в «Челси» за €80 млн в 2020 году.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/7/94/347179307185947.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Alex Grimm / Getty Images", + "rbc_news:description": "

Флориан Вирц

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/06/06/2024/664b07c09a79473d73c1b76c", + "rbc_news:title": "Украинский бомбардир и чудо «Байера». Главные сенсации топ-5 чемпионатов", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/79/347175838185797.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Aitor Alcalde / Getty Images", + "description": "

Футболисты ФК «Жирона»

" + } + }, + { + "@url": "https://www.rbc.ru/sport/18/05/2024/6648c0079a7947192b894493", + "rbc_news:title": "«Байер» завершил сезон в чемпионате Германии без поражений", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/9/75/347160438326759.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Stuart Franklin / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/sport/22/05/2024/664e000f9a794740e8fd15b2", + "rbc_news:title": "«Аталанта» Миранчука выиграла Лигу Европы, разгромив «Байер»", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/26/347163878988265.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Joern Pollex / Bongarts / Getty Images", + "description": "

Трофей Лиги Европы

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Большинство немцев выступили за возвращение обязательного призыва в армию", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/66657daf9a7947174df08354", + "attributes": {} + } + ], + "description": { + "content": "60% немцев выступили за возвращение обязательного призыва, еще 32% инициативу не поддержали, свидетельствуют результаты опроса YouGov. С марта число сторонников всеобщей воинской повинности увеличилось на 8%", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/58/347179281459583.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:66657daf9a7947174df08354", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:53:29+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:53:29", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/66657daf9a7947174df08354", + "rbc_news:anons": "60% немцев выступили за возвращение обязательного призыва, еще 32% инициативу не поддержали, свидетельствуют результаты опроса YouGov. С марта число сторонников всеобщей воинской повинности увеличилось на 8%", + "rbc_news:news_id": "66657daf9a7947174df08354", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717930409", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:00:14 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Германия", + "призыв в армию", + "Военная операция на Украине", + "Бундесвер" + ], + "rbc_news:full-text": "Большинство жителей Германии выступают за возвращение обязательного воинского призыва, свидетельствуют результаты опроса, проведенного институтом изучения общественного мнения YouGov по заказу газеты Welt am Sonntag.\n\nТак, возврат всеобщей воинской повинности в целом поддержали 60% опрошенных, при этом 28% выбрали вариант «полностью поддерживаю», а 32% — «отчасти поддерживаю». Против инициативы выступили 32%, в том числе 18% — «отчасти против» и 14% — «полностью». Оставшиеся 8% не смогли четко ответить на вопрос.\n\nОпрос также показал, что почти половина граждан Германии (49%) в настоящее время полагают, что ФРГ угрожают иностранные вооруженные силы. Несколько меньше (40%), напротив, не видят угрозы безопасности страны. Еще 11% не стали высказывать свою точку зрения по этому поводу.\n\nОпрос YouGo проводился с 31 мая по 5 июня. В нем приняли участие 2295 человек. В марте институт Forsa проводил аналогичный опрос. Тогда за возвращение призыва высказались 52% немцев, против — 43%.\n\n\n\nПризыв на военную службу в Германии был приостановлен в 2011 году. С тех пор численность бундесвера неуклонно сокращалась и сегодня составляет 181,5 тыс. солдат. Глава Минобороны Германии Борис Писториус в прошлом декабре называл ошибкой отмену обязательной службы и говорил, что получил порядка 65 предложений о реорганизации бундесвера. Spiegel в марте писал, что министр поручил подготовить предложения о модели набора на военную службу с элементами призыва.\n\nНа заседании исполкома партии СДПГ в мае Писториус представил новую модель службы, которая основана на добровольном привлечении молодых людей в армию, в том числе при помощи разных стимулов — бесплатного получения водительских прав, погашения части кредитов на обучение, языковых курсов. Уже в июне он заявил, что бундесвер должен быть готов к войне к 2029 году. «Мы не должны думать, что [президент России Владимир] Путин остановится у границ Украины», — отмечал министр.\n\nСам Путин неоднократно говорил о нежелании столкновения с альянсом. «Напридумывали, что Россия хочет напасть на НАТО. Вы сбрендили совсем, что ли? Тупые вообще, как этот стол?» — заявил он на встрече с иностранными корреспондентами 5 июня.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/3/58/347179281459583.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Alexander Koerner / Getty Images" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/politics/09/06/2024/666570559a794747ceae4f9c", + "rbc_news:title": "В Германии решили увеличить число резервистов", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/59/347179263271590.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Sean Gallup / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/politics/05/06/2024/66607dd99a7947c0e4e5476e", + "rbc_news:title": "Писториус заявил о необходимости подготовить Германию к войне к 2029 году", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/90/347176011297902.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Christoph Soeder / dpa / Global Look Press", + "description": "

Борис Писториус

" + } + }, + { + "@url": "https://www.rbc.ru/politics/17/02/2024/65d0da9d9a794700f7029c48", + "rbc_news:title": "Писториус предупредил о разделительных линиях между Россией и Западом", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/87/347081866152873.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Johannes Simon / Getty Images", + "description": "

Борис Писториус

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "В Херсонской области объявили авиационную опасность", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/666586ee9a7947ce6ee276fb", + "attributes": {} + } + ], + "description": { + "content": "В Херсонской области объявлена авиационная опасность, сообщил губернатор Владимир Сальдо в телеграм-канале.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/9/51/347179303402519.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666586ee9a7947ce6ee276fb", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:45:37+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:45:37", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/666586ee9a7947ce6ee276fb", + "rbc_news:anons": "В Херсонской области объявлена авиационная опасность, сообщил губернатор Владимир Сальдо в телеграм-канале.", + "rbc_news:news_id": "666586ee9a7947ce6ee276fb", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717929937", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 14:03:15 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Херсонская область", + "Владимир Сальдо" + ], + "rbc_news:full-text": "В Херсонской области объявлена авиационная опасность, сообщил губернатор Владимир Сальдо в телеграм-канале.\n\nОн призвал местных жителей быть внимательными и сохранять спокойствие.\n\nХерсонская область вошла в состав России по итогам референдума в сентябре 2022-го. В ноябре того же года российские войска отошли из Херсона на левый берег Днепра. Украина контролирует правый берег Днепра.\n\n\n\nВСУ 7 июня дважды обстреляли село Садовое в Херсонской области. Оба раза удар пришелся по местному магазину. В результате погибли 22 человека, еще 15 пострадали. В регионе объявили двухдневный траур, 9 и 10 июня. СК завел дело о теракте по ст. 205 УК. Российский МИД пообещал наказать причастных к обеим атакам.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/9/51/347179303402519.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "РИА Новости", + "rbc_news:description": "

Херсон

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/6664ba919a7947271a169b46", + "rbc_news:title": "Москалькова назвала бесчеловечными удары по ЛНР и Херсонской области" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6664582a9a79477626726f35", + "rbc_news:title": "В Херсонской области объявили траур по погибшим при обстреле села", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/87/347178532990876.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "SALDO_VGA / Telegram", + "description": "

Обстановка в селе Садовое, 7 июля 2024 г.

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6664154a9a79475c87a40140", + "rbc_news:title": "СК возбудил дело о теракте после обстрела села в Херсонской области", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/4/46/347178355570464.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "SALDO_VGA / Telegram" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Минобороны сообщило о сбитом украинском вертолете Ми-8", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/666584719a79473e002ab642", + "attributes": {} + } + ], + "description": { + "content": "Средства противовоздушной обороны сбили украинский вертолет Ми-8, сообщило Минобороны в ежедневной сводке.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/1/98/347179300588981.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666584719a79473e002ab642", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:36:12+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:36:12", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/666584719a79473e002ab642", + "rbc_news:anons": "Средства противовоздушной обороны сбили украинский вертолет Ми-8, сообщило Минобороны в ежедневной сводке.", + "rbc_news:news_id": "666584719a79473e002ab642", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717929372", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:55:01 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Минобороны", + "Вертолет Ми-8", + "ПВО", + "беспилотники", + "РСЗО HIMARS" + ], + "rbc_news:full-text": "Средства противовоздушной обороны сбили украинский вертолет Ми-8, сообщило Минобороны в ежедневной сводке.\n\nВ ведомстве не уточнили, где он был сбит. По оценке Минобороны, с начала военной операции России на Украине было уничтожено 276 вертолетов ВСУ. Кроме того, за сутки военные перехватили 66 беспилотников и 13 реактивных снарядов HIMARS американского производства.\n\nПомимо этого ВСУ потеряли боевые машины пехоты Marder немецкого производства, американские Bradley, а также два автомобиля, заявили в российском военном ведомстве.\n\n\n\nМинобороны регулярно сообщает о ходе военной операции. Накануне, 8 июня, ведомство отчиталось об уничтожении вертолета Ми-8, пяти безэкипажных катеров в Черном море, четырех ракет ATACMS, снарядов HIMARS и «Ольха», одной ракеты «Нептун» и 71 дрона.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/1/98/347179300588981.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Alex Babenko / Getty Images", + "rbc_news:description": "

Вертолёт Ми-8

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/politics/09/06/2024/6665736d9a7947fffe234918", + "rbc_news:title": "В Минобороны Украины назвали число женщин в рядах ВСУ", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/78/347179254085788.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Leon Neal / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/666437aa9a7947362b7792e4", + "rbc_news:title": "Минобороны сообщило о сбитом украинском вертолете Ми-8", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/4/36/347178457849364.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Alex Babenko / Getty Images", + "description": "

Вертолёт Ми-8

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/66641c549a794769a05c58c4", + "rbc_news:title": "Минобороны сообщило о трех сбитых беспилотниках над Белгородской областью", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/29/347178376874296.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Николай Гынгазов / ТАСС" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Будущий игрок «Реала» Эндрик повторил достижение Пеле в сборной Бразилии", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/66657dcc9a794797be6910e8", + "attributes": {} + } + ], + "description": { + "content": "Бразильцы победили мексиканцев со счетом 3:2. Победный гол забил 17-летний Эндрик, который отличился за сборную в третьем матче подряд. Ранее такую результативность до 18 лет в сборной Бразилии показывал только Пеле", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/69/347179274026698.png", + "type": "image/png", + "length": "" + } + }, + { + "content": null, + "attributes": { + "url": "https://img.youtube.com/vi/d89n5rTI-q4/0.jpg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:66657dcc9a794797be6910e8", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:29:42+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:29:42", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/66657dcc9a794797be6910e8", + "rbc_news:anons": "Бразильцы победили мексиканцев со счетом 3:2. Победный гол забил 17-летний Эндрик, который отличился за сборную в третьем матче подряд. Ранее такую результативность до 18 лет в сборной Бразилии показывал только Пеле", + "rbc_news:news_id": "66657dcc9a794797be6910e8", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717928982", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:36:59 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "Футбол", + "Сборная Бразилии по футболу", + "Пеле" + ], + "rbc_news:full-text": "Нападающий «Палмейрас» Эндрик принес сборной Бразилии победу в товарищеском матче с мексиканцами, став вторым в истории национальной команды футболистом, который забил в трех матчах подряд до 18-летия.\n\nБразильцы победили мексиканцев со счетом 3:2 в матче, который прошел в городе Колледж-Стейшен в Техасе (США).\n\n17-летний Эндрик отличился на 96-й минуте с передачи Винисиуса Жуниора.\n\n\n\nЭндрик в июле, после достижения 18-летия, станет игроком мадридского «Реала».\n\nГол в ворота Мексики стал для Эндрика третьим в трех матчах за сборную подряд: в марте он забил Англии (1:0) и Испании (3:3, а всего провел пять встреч за основную сборную.\n\nВ сборной Бразилии ранее только Пеле удавалось забить в трех матчах подряд в возрасте младше 18 лет. Впервые он забил три мяча в двух товарищеских матчах с аргентинцами и встрече со сборной Парагвая. А позднее на чемпионате мира 1958 года отличился в четвертьфинале, полуфинале и финале.\n\nПеле умер в возрасте 82 лет в декабре 2022 года.\n\nСборная Бразилии примет участие в Кубке Америки, который пройдет в США с 20 июня по 14 июля. Бразильцы сыграют в одной группе с Колумбией, Парагваем и Коста-Рикой.", + "rbc_news:image": [ + { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/8/69/347179274026698.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Tim Warner / Getty Images", + "rbc_news:description": "

Эндрик (в центре)

" + }, + { + "rbc_news:url": "https://img.youtube.com/vi/d89n5rTI-q4/0.jpg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright" + } + ], + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/27/03/2024/6603e7729a794714afebf60b", + "rbc_news:title": "Эндрик в 17 лет улучшил достижение Пеле в сборной Бразилии", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/9/81/347115319717819.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Juan Medina / Reuters", + "description": "

Эндрик

" + } + }, + { + "@url": "https://www.rbc.ru/sport/07/12/2023/65715f959a7947118d485006", + "rbc_news:title": "Клуб Пеле впервые вылетел из элитной лиги и вызвал беспорядки в Бразилии", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/38/347019316692383.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "ФК «Форталезе»" + } + }, + { + "@url": "https://www.rbc.ru/sport/15/05/2024/6644827e9a7947592842bfbd", + "rbc_news:title": "В Бразилии узнали о желании «Краснодара» купить Джона Кеннеди", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/17/347157660389177.png", + "type": "image/png", + "size_nick": "original", + "copyright": "limited", + "source": "Alexandre Neto / Keystone Press Agency / Global Look Press", + "description": "

Джон Кеннеди

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Орбан рассказал, от чего зависит мир на Украине", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66657b489a794731ffe7280a", + "attributes": {} + } + ], + "description": { + "content": "Урегулировать конфликт на Украине можно будет в случае победы миролюбивых сил на выборах в Европарламент и США, считает премьер-министр Венгрии Виктор Орбан, передает Híradó.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/36/347179274146362.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:66657b489a794731ffe7280a", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:19:30+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:19:30", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66657b489a794731ffe7280a", + "rbc_news:anons": "Урегулировать конфликт на Украине можно будет в случае победы миролюбивых сил на выборах в Европарламент и США, считает премьер-министр Венгрии Виктор Орбан, передает Híradó.", + "rbc_news:news_id": "66657b489a794731ffe7280a", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717928370", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:27:05 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Виктор Орбан", + "Евросоюз", + "США", + "Украина", + "Военная операция на Украине" + ], + "rbc_news:full-text": "Урегулировать конфликт на Украине можно будет в случае победы миролюбивых сил на выборах в Европарламент и США, считает премьер-министр Венгрии Виктор Орбан, передает Híradó.\n\nПо его мнению, главной темой проходящих сейчас выборов в Европарламент во Франции, Германии, Италии, других странах ЕС стал «вопрос о войне и мире». «Это половина работы, другую половину должен будет сделать американский народ на президентских выборах. И тогда может быть мир», — добавил Орбан.\n\n\n\nГолосование на выборах в Европарламент в большинстве стран, в том числе в Венгрии, проходит 9 июня, в так называемое большое воскресенье — финальный день выборов.\n\nВыборы президента США запланированы на 5 ноября. Основные кандидаты — бывший глава государства Дональд Трамп и действующий — Джо Байден.\n\nМосква для урегулирования украинского конфликта требует признания «новых территориальных реалий» — Донецкой и Луганской народных республик, Запорожской и Херсонской областей в составе России. Президент Украины Владимир Зеленский называл одним из условий начала переговоров возвращение к границам страны 1991 года.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/2/36/347179274146362.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Bernadett Szabo / Reuters", + "rbc_news:description": "

Виктор Орбан

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/politics/09/06/2024/6664f7cb9a7947bc1553f868", + "rbc_news:title": "Орбан заявил, что Запад хочет победить Россию ради денег", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/15/347178955131155.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Bernadett Szabo / Reuters", + "description": "

Виктор Орбан

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6462028e9a79477803bdaffd", + "rbc_news:title": "В Польше посчитали преждевременными мирные переговоры России и Украины", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/27/346841457775272.jpg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "president.pl", + "description": "

Марчин Пшидач

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6664c3929a794733d9d0f120", + "rbc_news:title": "На Украине захотели собрать коалицию для информационных атак на Россию" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "В Минобороны Украины назвали число женщин в рядах ВСУ", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/6665736d9a7947fffe234918", + "attributes": {} + } + ], + "description": { + "content": "В ВСУ служат свыше 67 тыс. женщин, из них 19 тыс. — гражданские сотрудницы, заявила замминистра обороны Украины. В 2014 году, по оценкам ведомства, этот показатель составлял почти 50 тыс.", + "attributes": {} + }, + "author": { + "content": "Юлия Овчинникова", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/78/347179254085788.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:6665736d9a7947fffe234918", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:09:35+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:09:35", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/6665736d9a7947fffe234918", + "rbc_news:anons": "В ВСУ служат свыше 67 тыс. женщин, из них 19 тыс. — гражданские сотрудницы, заявила замминистра обороны Украины. В 2014 году, по оценкам ведомства, этот показатель составлял почти 50 тыс.", + "rbc_news:news_id": "6665736d9a7947fffe234918", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717927775", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:21:15 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Россия", + "Украина", + "Минобороны Украины", + "женщины" + ], + "rbc_news:full-text": "В Вооруженных силах Украины (ВСУ) сейчас служат более 67 тыс. женщин, из которых 19 тыс. — гражданские сотрудницы, заявила заместитель министра обороны Украины Наталия Калмыкова. Ее интервью опубликовано в YouTube-канале украинского Минобороны.\n\n«Мобилизация у нас не предусматривает призыв женщин, но это не является препятствием, и их число в армии постоянно увеличивается. Сейчас у нас в ВСУ 67 тыс. женщин, из которых 19 тыс. — это гражданские сотрудницы, остальные — военнослужащие», — сказала она.\n\nПо ее словам, росту числа женщин в рядах ВСУ способствовало начало военной операции на Украине, однако они получили право занимать любые должности в армии еще в 2018 году за счет изменения законодательства. В частности, сейчас они могут управлять артиллерией и дронами, отметила Калмыкова.\n\nСогласно данным Минобороны Украины, в октябре 2023 года численность женщин в ВСУ составляла 62 тыс. по сравнению с почти 50 тыс. в 2014-м. Этот показатель Калмыкова называла рекордным в новейшей истории страны.\n\n24 февраля 2022 года на Украине объявили всеобщую мобилизацию и военное положение, действие которых в дальнейшем неоднократно продлевалось.\n\nПрезидент Украины Владимир Зеленский 16 апреля 2024 года ужесточил условия мобилизации. В частности, возраст призыва был снижен с 27 до 25 лет, а также отменена категория ограниченно годных. Всех украинских мужчин в возрасте 18–60 лет обязали носить с собой военный билет вне зависимости от их годности к службе и права на отсрочку, а военнообязанных — обновить данные в территориальных центрах комплектования (аналог военкомата) в течение 60 дней.\n\nВ конце 2023 года Зеленский рассказал о просьбе Генштаба и главкома ВСУ дополнительно призвать на службу 450–500 тыс. человек. Он оценил затраты на проведение дополнительной мобилизации в 500 млрд грн (около $13 млрд).", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/8/78/347179254085788.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Leon Neal / Getty Images" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/society/06/01/2024/659939099a79471312811618", + "rbc_news:title": "«Мобилизация» стала словом года на Украине", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/45/347045423790453.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "John Moore / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/politics/24/12/2023/65883af19a7947b205a3791c", + "rbc_news:title": "В Киеве рассказали, когда начнется демобилизация на Украине", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/01/347034285362016.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "John Moore / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/politics/01/06/2024/665b40459a7947332d980272", + "rbc_news:title": "Депутат Рады заявил, что мобилизация затронет спасателей и пожарных", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/97/347172567331977.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "dsns_telegram / Telegram" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "В Миргороде прогремели взрывы", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/666579f39a79471a2aa3bb18", + "attributes": {} + } + ], + "description": { + "content": "Взрывы прогремели в украинском Миргороде, где расположен крупный военный аэродром. Воздушная тревога в настоящее время объявлена в четырех областях Украины", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/23/347179272525230.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666579f39a79471a2aa3bb18", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T13:04:35+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "13:04:35", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/666579f39a79471a2aa3bb18", + "rbc_news:anons": "Взрывы прогремели в украинском Миргороде, где расположен крупный военный аэродром. Воздушная тревога в настоящее время объявлена в четырех областях Украины", + "rbc_news:news_id": "666579f39a79471a2aa3bb18", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717927475", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:54:10 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Полтавская область", + "взрывы", + "Военная операция на Украине", + "военные аэродромы" + ], + "rbc_news:full-text": "Взрывы прозвучали в Миргороде Полтавской области, сообщают «Зеркало недели» и «Страна.ua». Воздушная тревога объявлена в Черниговской, Полтавской, Сумской и Николаевской областях Украины.\n\nВ черте Миргорода находится военный аэродром, об ударах по которому ранее неоднократно отчитывалось Минобороны России. По данным ведомства, с начала спецоперации российскими силами были уничтожены 610 самолетов и 275 вертолетов ВВС Украины.\n\n\n\nРанее этой ночью украинские СМИ сообщали о взрывах в Николаеве и Одессе. Минобороны России подчеркивает, что удары наносятся только по военным и энергетическим объектам Украины и связанной с ними инфраструктуре.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/0/23/347179272525230.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "сервис «Яндекс.Карты»" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/66652a3f9a7947ecba74c931", + "rbc_news:title": "В Одессе прогремел взрыв" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6664d21e9a79478069098562", + "rbc_news:title": "В Николаеве прогремел взрыв" + }, + { + "@url": "https://www.rbc.ru/politics/09/06/2024/66653d869a79474ab771afb5", + "rbc_news:title": "МЧС сообщило о спасении семи человек из-под завалов в Луганске", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/48/347179117697483.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Александр Река / ТАСС" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Уроженец Дагестана Имавов победил американца в главном бою турнира UFC", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/666576bf9a794710b7db95ad", + "attributes": {} + } + ], + "description": { + "content": "Судья остановил бой в четвертом раунде, когда Каннонье еще был на ногах и в сознании после серии ударов Имавова, что вызвало недовольство болельщиков. Имавов одержал 14-ю победу в ММА", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/78/347179267448786.png", + "type": "image/png", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:666576bf9a794710b7db95ad", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:58:17+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:58:17", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/666576bf9a794710b7db95ad", + "rbc_news:anons": "Судья остановил бой в четвертом раунде, когда Каннонье еще был на ногах и в сознании после серии ударов Имавова, что вызвало недовольство болельщиков. Имавов одержал 14-ю победу в ММА", + "rbc_news:news_id": "666576bf9a794710b7db95ad", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717927097", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:07:13 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "смешанные единоборства (ММА)", + "UFC" + ], + "rbc_news:full-text": "Родившийся в Дагестане французский боец Нассурдин Имавов одержал победу техническим нокаутом над американцем Джаредом Каннонье в главном поединке турнира UFC в Луисвилле (штат Кентукки, США).\n\nПоединок в среднем весе (до 84 кг) завершился в четвертом раунде.\n\nСудья остановил бой на второй минуте раунда, когда американец после серии ударов Имавова еще был в сознании и на ногах, однако болельщики посчитали, что он поторопился, и выразили недовольство.\n\nИмавов после боя заявил, что готов был продолжать поединок, если бы рефери позволил, но при этом считает, что решение судьи было правильным, пишет MMA Fighting.\n\n\n\nИмавов одержал 14-ю победу в ММА при четырех поражениях (еще один бой был признан несостоявшимся).\n\nКаннонье потерпел седьмое поражение при 17 победах.\n\nИмавов родился в Хасавюрте, в юности переехал во Францию и представляет эту страну. Выступает в UFC с 2020 года, одержал в промоушене шесть побед, два боя проиграл. В рейтинге UFC занимает седьмое место.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/6/78/347179267448786.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Jeff Bottari / Zuffa LLC via Getty Images", + "rbc_news:description": "

Нассурдин Имавов

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/05/06/2024/66601b2f9a79478709a80270", + "rbc_news:title": "Российский бизнесмен купил за $95 тыс. тренировку c экс-чемпионом UFC", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/60/347175745717600.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Global Look Press", + "description": "

Чарльз Оливейра

" + } + }, + { + "@url": "https://www.rbc.ru/sport/06/06/2024/666163649a7947ae5601212a", + "rbc_news:title": "Порье рассказал о полученных в чемпионском бою UFC с Махачевым переломах", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/27/347176585073277.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Luke Hales / Getty Images", + "description": "

Дастин Порье и Ислам Махачев

" + } + }, + { + "@url": "https://www.rbc.ru/sport/02/06/2024/665c39189a7947c2f637e0ac", + "rbc_news:title": "Хабиб Нурмагомедов встретился с Трампом на турнире UFC", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/88/347173202481885.png", + "type": "image/png", + "size_nick": "original", + "copyright": "limited", + "source": "Luke Hales / Getty Images", + "description": "

Дональд Трамп

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Захарова заявила о «кознях Запада» перед ПМЭФ", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/6665767e9a794710b7db95a8", + "attributes": {} + } + ], + "description": { + "content": "Запад строил козни перед Петербургским международным экономическим форумом (ПМЭФ), но Россия выстраивает отношения с мировым большинством.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/68/347179269365682.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:6665767e9a794710b7db95a8", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:55:00+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:55:00", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/6665767e9a794710b7db95a8", + "rbc_news:anons": "Запад строил козни перед Петербургским международным экономическим форумом (ПМЭФ), но Россия выстраивает отношения с мировым большинством.", + "rbc_news:news_id": "6665767e9a794710b7db95a8", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717926900", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:03:51 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Мария Захарова", + "МИД", + "аккредитация", + "ПМЭФ" + ], + "rbc_news:full-text": "Запад строил козни перед Петербургским международным экономическим форумом (ПМЭФ), но Россия выстраивает отношения с мировым большинством. Об этом на полях мероприятия заявила ТАСС официальный представитель МИДа Мария Захарова.\n\n«Да, западники всегда будут строить козни. И здесь, [на форуме], и когда мы едем куда-то», — сказала она. По словам Захаровой, российским дипломатам не дают визы, пролетные документы, аккредитации, угрожают не разрешать посадку самолета.\n\nРоссия, в свою очередь, занимается конструктивной повесткой и выстраиванием отношений с мировым большинством, отметила представитель МИДа. В пример она привела африканское турне министра иностранных дел Сергея Лаврова. «Вот там как раз и развивается то, что мы называем многополярностью, причем об этом говорят все», — заключила она.\n\n\n\nМИД считает «дискриминацией по национальному признаку» отказ в аккредитациях на международные мероприятия, которые участились после начала Россией военной операции на Украине. В частности, в середине мая Россия получила отказ от властей Нидерландов в аккредитации на международную конференцию ЮНЕСКО, которая должна была пройти в Гааге.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/2/68/347179269365682.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Александр Демьянчук / ТАСС", + "rbc_news:description": "

Мария Захарова

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/business/08/06/2024/6664337e9a7947f093d718c8", + "rbc_news:title": "Организаторы ПМЭФ раскрыли общую сумму заключенных контрактов", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/13/347178455295132.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Михаил Гребенщиков / РБК" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6662f7189a7947eeb386f2c3", + "rbc_news:title": "Путин встретился с Бегловым перед пленарным заседанием ПМЭФ", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/68/347177650436685.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Александр Казаков / РИА Новости", + "description": "

Владимир Путин и Александр Беглов

" + } + }, + { + "@url": "https://www.rbc.ru/politics/07/06/2024/6662b0509a7947a447d4c3d0", + "rbc_news:title": "Выступление Путина на пленарном заседании ПМЭФ. Видео" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "В Германии решили увеличить число резервистов", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/666570559a794747ceae4f9c", + "attributes": {} + } + ], + "description": { + "content": "Власти Германии на фоне «вызовов в сфере безопасности» планируют довести количество военных резервистов до 60 тыс., сообщили в бундесвере. Там уточнили, что в рамках военного закона могут быть призваны 800 тыс. немцев", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/59/347179263271590.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666570559a794747ceae4f9c", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:52:02+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:52:02", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/666570559a794747ceae4f9c", + "rbc_news:anons": "Власти Германии на фоне «вызовов в сфере безопасности» планируют довести количество военных резервистов до 60 тыс., сообщили в бундесвере. Там уточнили, что в рамках военного закона могут быть призваны 800 тыс. немцев", + "rbc_news:news_id": "666570559a794747ceae4f9c", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717926722", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:03:56 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Германия", + "Бундесвер", + "резервисты", + "Военная операция на Украине", + "перевооружение" + ], + "rbc_news:full-text": "Министерство обороны Германии разрабатывает планы по созданию усиленного резерва бундесвера (ВС ФРГ), в котором будут состоять как минимум 60 тыс. человек, заявил генерал-лейтенант, заместитель генерального инспектора бундесвера Александр Хоппе, передает Zeit.\n\nПо словам немецкого генерала, новые резервисты, в числе которых будут и мужчины, и женщины, должны обучаться как во времена холодной войны, чтобы могли усиливать или заменять действующие войска в бою. «Я убежден, что мы должны адаптировать резерв к текущим вызовам политики безопасности, чтобы тот мог должным образом поддерживать бундесвер в выполнении задач национальной обороны и обороны альянса», — сказал Хоппе.\n\nЦелью военных служит наличие в будущем до 60 тыс. резервистов так называемого базового назначения, то есть квалифицированных и способных выполнять постоянные задачи в этом статусе. Минобороны также изучает, насколько велико число граждан, которые вообще могут быть призваны и пригодны к службе в случае обороны. «Там разные цифры. Мы предполагаем, что есть около 800 тыс., которые также могут быть призваны по военному закону», — отметил генерал.\n\nПризыв на военную службу в Германии был приостановлен в 2011 году, а численность бундесвера с тех пор неуклонно сокращалась и сегодня составляет 181,5 тыс. солдат. По данным Welt, Минобороны ФРГ при этом располагает лишь 44 тыс. резервистами, получившими базовую квалификацию. Как отмечает Spiegel, планы НАТО подразумевают увеличение численности личного состава вооруженных сил Германии до 272 тыс. мужчин и женщин.\n\n\n\nВ начале июня глава комитета бундестага по обороне Мари-Агнес Штрак-Циммерман призвала поставить на учет 900 тыс. резервистов на фоне «российской угрозы». Министр обороны Германии Борис Писториус при этом заявлял, что бундесвер должен быть готов к войне к 2029 году. «Мы не должны думать, что Путин остановится у границ Украины», — отмечал он.\n\nBild в начале июня опубликовал документ с планом властей Германии на случай войны. Из него в том числе следует, что в стране в случае конфликта возобновится обязательная военная служба. Служба занятости при этом сможет обязать граждан старше 18 лет работать на определенной работе, к примеру на почте и в пекарнях. Одновременно власти начнут массовую эвакуацию, а через страну пойдут эшелоны войск НАТО.\n\nРоссийская сторона не раз отвергала планы войны с НАТО. Президент Владимир Путин говорил о нежелании столкновения с альянсом. При этом он отмечал, что конфликт на Украине связан с «созданием угроз безопасности России со стороны США и НАТО», которые при этом отказываются от переговоров.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/0/59/347179263271590.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Sean Gallup / Getty Images" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/6663607a9a79478abd18be45", + "rbc_news:title": "Bloomberg узнал, что Германия готова поставить Украине еще один Patriot", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/4/47/347177893251474.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Karl-Josef Hildenbrand / dpa / Global Look Press", + "description": "

Пусковая установка зенитно-ракетного комплекса Patriot

" + } + }, + { + "@url": "https://www.rbc.ru/politics/06/06/2024/6661a1a29a7947062c42ce1a", + "rbc_news:title": "Шольц пообещал не допустить войну в Германии", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/17/347176779986170.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Henning Schacht / Keystone Press Agency / Global Look Press", + "description": "

Олаф Шольц

" + } + }, + { + "@url": "https://www.rbc.ru/politics/05/06/2024/66607dd99a7947c0e4e5476e", + "rbc_news:title": "Писториус заявил о необходимости подготовить Германию к войне к 2029 году", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/90/347176011297902.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Christoph Soeder / dpa / Global Look Press", + "description": "

Борис Писториус

" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Гладков рассказал о последствиях атаки на Шебекино", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/666574689a7947fc21cd7a6a", + "attributes": {} + } + ], + "description": { + "content": "Под обстрел со стороны Украины попал Шебекинский городской округ, никто не пострадал, сообщил губернатор Белгородской области Вячеслав Гладков.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/26/347179258202263.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666574689a7947fc21cd7a6a", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:30:57+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:30:57", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/666574689a7947fc21cd7a6a", + "rbc_news:anons": "Под обстрел со стороны Украины попал Шебекинский городской округ, никто не пострадал, сообщил губернатор Белгородской области Вячеслав Гладков.", + "rbc_news:news_id": "666574689a7947fc21cd7a6a", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717925457", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:37:07 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Шебекино", + "Белгородская область", + "Вячеслав Гладков", + "обстрел" + ], + "rbc_news:full-text": "Под обстрел со стороны Украины попал Шебекинский городской округ, никто не пострадал, сообщил губернатор Белгородской области Вячеслав Гладков.\n\nОб обстреле Шебекино и округа региональное управление МЧС предупредило в 9:40 мск.\n\nВ городе повреждены два автобуса и один автомобиль, сообщил глава региона. В селе Муром дрон сбросил взрывное устройство — в двух частных домах выбило окна, повредило фасады, заборы и кровли. Кроме того, пострадал автомобиль.\n\n\nШебекино расположен менее чем в 5 км от границы с Харьковской областью Украины, это ближайший к российско-украинской границе город Белгородской области. Он регулярно попадает под обстрелы.\n\n\n\n\nБелгородская область в последние месяцы подвергается интенсивным обстрелам, в регионе периодически объявляют ракетную опасность, сбивают украинские ракеты и дроны.\n\nРанее Гладков сообщил об уничтожении воздушных целей над Новооскольским городским округом. По его словам, никто не пострадал. В нескольких частных и многоквартирных домах выбило окна, пострадали несколько автомобилей.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/3/26/347179258202263.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Алексей Гавриш / ТАСС" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/66656bfb9a794747ceae4f91", + "rbc_news:title": "Над Белгородской областью сбили ракету «Нептун» и два дрона", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/74/347179234584748.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Николай Гынгазов / ТАСС" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6665564c9a7947d363ac1ebd", + "rbc_news:title": "В Воронежской области вслед за Белгородом объявили ракетную опасность", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/90/347179180733905.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Сергей Бобылев / ТАСС", + "description": "

Воронеж

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/666551a39a7947eab562cb15", + "rbc_news:title": "В Белгороде объявили ракетную опасность" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Жителей Белгорода во второй раз за день предупредили о ракетной опасности", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/6665750c9a794710b7db959e", + "attributes": {} + } + ], + "description": { + "content": "В Белгороде и Белгородском районе вновь ввели режим ракетной опасности, сообщил губернатор региона Вячеслав Гладков в телеграм-канале.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/40/347179255944402.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:6665750c9a794710b7db959e", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:29:05+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:29:05", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/6665750c9a794710b7db959e", + "rbc_news:anons": "В Белгороде и Белгородском районе вновь ввели режим ракетной опасности, сообщил губернатор региона Вячеслав Гладков в телеграм-канале.", + "rbc_news:news_id": "6665750c9a794710b7db959e", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717925345", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:33:36 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Белгород", + "ракетная опасность", + "Вячеслав Гладков" + ], + "rbc_news:full-text": "В Белгороде и Белгородском районе вновь ввели режим ракетной опасности, сообщил губернатор региона Вячеслав Гладков в телеграм-канале.\n\nОн призвал местных жителей спуститься в подвалы и оставаться там до получения сигнала «отбой ракетной опасности».\n\nТакой сигнал в Белгороде и Белгородском районе запускают во второй раз за день. Ранее он действовал с 9:50 по 10:15 мск. В это время силы ПВО сбили украинскую ракету «Нептун».\n\n\n\nБелгородская область в последние месяцы подвергается интенсивным обстрелам, в регионе периодически объявляют ракетную опасность, сбивают украинские ракеты и дроны. В Белгороде продолжают устанавливать бетонные автобусные остановки и модульные железобетонные укрытия.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/2/40/347179255944402.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Александр Рюмин / ТАСС", + "rbc_news:description": "

Белгород

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/66656bfb9a794747ceae4f91", + "rbc_news:title": "Над Белгородской областью сбили ракету «Нептун» и два дрона", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/74/347179234584748.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Николай Гынгазов / ТАСС" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6665564c9a7947d363ac1ebd", + "rbc_news:title": "В Воронежской области вслед за Белгородом объявили ракетную опасность", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/90/347179180733905.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Сергей Бобылев / ТАСС", + "description": "

Воронеж

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/666551a39a7947eab562cb15", + "rbc_news:title": "В Белгороде объявили ракетную опасность" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "На петербургской «СКА Арене» за ₽60 млрд произошел пожар", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/66656e3c9a794764ba47759b", + "attributes": {} + } + ], + "description": { + "content": "Арену ввели в эксплуатацию в конце прошлого года, на ней проводит домашние матчи хоккейный клуб СКА. Ночью в чаше стадиона произошел пожар, пострадали трибуны и рекламные щиты", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/96/347179243148960.png", + "type": "image/png", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:66656e3c9a794764ba47759b", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:24:07+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:24:07", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/66656e3c9a794764ba47759b", + "rbc_news:anons": "Арену ввели в эксплуатацию в конце прошлого года, на ней проводит домашние матчи хоккейный клуб СКА. Ночью в чаше стадиона произошел пожар, пострадали трибуны и рекламные щиты", + "rbc_news:news_id": "66656e3c9a794764ba47759b", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717925047", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:29:00 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "Хоккей", + "ХК СКА Санкт-Петербург" + ], + "rbc_news:full-text": "Пожар произошел в ночь на 9 июня в чаше спортивного комплекса «СКА Арена» в Санкт-Петербурге, которая была введена в эксплуатацию в конце прошлого года, сообщают «Фонтанка» и 78.ru.\n\nПлощадь возгорания составила 225 кв. м, после вызова пожарных в 1:46 мск его ликвидировали менее чем за полчаса.\n\nОт огня пострадали часть трибун и рекламные щиты. По предварительной информации, причиной пожара стала неисправность проводки.\n\nРБК обратился за комментарием в пресс-службу управления МЧС по Санкт-Петербургу и «СКА Арену».\n\n\n\nГендиректор стадиона Дмитрий Сватковский в ноябре заявил, что стоимость арены составляет около 60 млрд руб.\n\nНа арене проводит домашние матчи хоккейный клуб СКА, на играх которого был установлен рекорд посещаемости для крытых стадионов (почти 24 тыс. зрителей). В декабре там также прошел Матч звезд КХЛ.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/0/96/347179243148960.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Валентина Певцова / ТАСС", + "rbc_news:description": "

Матч звезд КХЛ на «СКА Арене»

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/07/05/2024/663a66f99a7947cf3edb9942", + "rbc_news:title": "Экс-футболист «Челси» помог спасти 100 человек от наводнения в Бразилии", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/38/347151035455383.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Wagner Meier / Getty Images", + "description": "

Диего Коста

" + } + }, + { + "@url": "https://www.rbc.ru/sport/26/02/2024/65dc708f9a7947312c275fd6", + "rbc_news:title": "Рудковская опровергла информацию об обвалившейся крыше академии Плющенко", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/37/347089463952377.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Вячеслав Прокофьев / ТАСС", + "description": "

Евгений Плющенко

" + } + }, + { + "@url": "https://www.rbc.ru/sport/04/03/2024/65e5fcec9a794738ffd036b0", + "rbc_news:title": "РФС разрешил «Спартаку» вернуться на домашний стадион после ЧП с травой" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Россияне получили восемь медалей на Азиатской олимпиаде по физике", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66656f199a794764ba4775a1", + "attributes": {} + } + ], + "description": { + "content": "Российские школьники завоевали пять золотых и три серебряные медали на 24-й Азиатской физической олимпиаде, сообщили РБК в пресс-службе Минпросвещения.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Общество", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/98/347179249039985.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:society:66656f199a794764ba4775a1", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:18:46+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:18:46", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66656f199a794764ba4775a1", + "rbc_news:anons": "Российские школьники завоевали пять золотых и три серебряные медали на 24-й Азиатской физической олимпиаде, сообщили РБК в пресс-службе Минпросвещения.", + "rbc_news:news_id": "66656f199a794764ba4775a1", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717924726", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:25:40 +0300", + "rbc_news:newsline": "society", + "rbc_news:tag": [ + "физика", + "школьная олимпиада", + "школьники", + "россияне", + "Минпросвещения" + ], + "rbc_news:full-text": "Российские школьники завоевали пять золотых и три серебряные медали на 24-й Азиатской физической олимпиаде, сообщили РБК в пресс-службе Минпросвещения.\n\nАзиатская физическая олимпиада (Asian Physics Olympiad, APhO) — ежегодное соревнование, состоящее из теоретического и практического тура. В этом году она прошла в Малайзии и включала 28 команд старшеклассников из 27 стран.\n\n\n\nВ прошлом году российские школьники получили восемь золотых медалей на APhO, которая проходила в Монголии. По ее итогам формировали российскую сборную для участия на 53-й Международной физической олимпиаде школьников в Токио, где россияне получили пять золотых медалей.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/5/98/347179249039985.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "пресс-служба Минпросвещения России" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/07/06/2024/6661a38d9a79475bdcea5fe2", + "rbc_news:title": "Как российская школьница привела в восторг звезд тенниса", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/1/95/347177360462951.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Reuters", + "description": "

Мирра Андреева

" + } + }, + { + "@url": "https://www.rbc.ru/society/06/06/2024/666199fb9a79472fbec7ee60", + "rbc_news:title": "На ЕГЭ по литературе 100 баллов получили более 1700 школьников", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/67/347176799049676.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Кирилл Кухмарь / ТАСС" + } + }, + { + "@url": "https://www.rbc.ru/society/03/06/2024/665db0999a7947d3cf3631b4", + "rbc_news:title": "Прокуратура проверит чувашскую школу из-за раздевания школьниц перед ЕГЭ", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/76/347174196322765.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Александр Щербак / ТАСС" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Times узнала, что легенда велоспорта может потерять трофеи из-за долгов", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/sport/09/06/2024/666564dc9a794773c50876ab", + "attributes": {} + } + ], + "description": { + "content": "Лучшего велогонщика в истории Великобритании признали банкротом в начале июня из-за долга его компании, который превысил £1 млн. У Брэдли Уиггинса могут изъять даже награды и кубки, как это произошло с Борисом Беккером", + "attributes": {} + }, + "author": { + "content": "Анна Сатдинова", + "attributes": {} + }, + "category": [ + { + "content": "Спорт", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/65/347179210481657.png", + "type": "image/png", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:sport:666564dc9a794773c50876ab", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:12:11+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:12:11", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/sport/09/06/2024/666564dc9a794773c50876ab", + "rbc_news:anons": "Лучшего велогонщика в истории Великобритании признали банкротом в начале июня из-за долга его компании, который превысил £1 млн. У Брэдли Уиггинса могут изъять даже награды и кубки, как это произошло с Борисом Беккером", + "rbc_news:news_id": "666564dc9a794773c50876ab", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717924331", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:23:17 +0300", + "rbc_news:newsline": "sport", + "rbc_news:tag": [ + "велоспорт", + "Великобритания", + "банкротства" + ], + "rbc_news:full-text": "Пятикратный олимпийский чемпион по велоспорту британец Брэдли Уиггинс после признания банкротом может лишиться своих наград, как это произошло в 2022 году с теннисистом Борисом Беккером, пишет The Times.\n\nУиггинс — первый велогонщик, выигравший золото Олимпиад и чемпионатов мира и на треке, и на шоссе, а также победивший на «Тур де Франс» (в 2012-м). Он завершил карьеру в 2016 году.\n\nВ 2020 году компания Wiggins Rights Limited, которой экс-велогонщик владел вместе с матерью и бывшей женой, объявила о ликвидации с долгами в размере £650 тыс. ($825 тыс.). Причем одним из кредиторов было Управление по налоговым и таможенным сборам Великобритании, которое в прошлом году заявило, что компания должна ему более £300 тыс.\n\nВ 2022 году Уиггинс заключил соглашение, которое должно было помочь расплатиться, но к 2023-му долг вырос почти до £1 млн.\n\n3 июня Уиггинса объявили банкротом, специальный управляющий будет изымать его активы, в том числе, возможно, медали и кубки, как в деле Беккера в 2022-м, которому пришлось отдать свои трофеи Уимблдона.\n\n\n\nВсего на счету Уиггинса — восемь медалей Игр в Сиднее, Афинах, Пекине, Лондоне и Рио-де-Жанейро и восемь побед на чемпионатах мира в 2000–2016 годах.\n\nВелогонщик в 2013 году был посвящен в рыцари после победы на домашней Олимпиаде и в «Тур де Франс».\n\nБывший немецкий теннисист Беккер был признан банкротом в 2017 году из-за невыплаченного кредита в размере более €3,6 млн за недвижимость на Майорке. В апреле 2022-го его приговорили к двум с половиной годам заключения за нарушение закона о банкротстве, но уже в декабре выпустили из британской тюрьмы с целью депортации на родину.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/7/65/347179210481657.png", + "rbc_news:type": "image/png", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Doug Pensinger / Getty Images", + "rbc_news:description": "

Брэдли Уиггинс

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/sport/06/06/2024/66618b149a79477a208934f0", + "rbc_news:title": "Российские велогонщики завоевали три лицензии для участия в Олимпиаде", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/2/03/347176686995032.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Tim de Waele / Getty Images", + "description": "

Александр Власов

" + } + }, + { + "@url": "https://www.rbc.ru/sport/16/03/2024/65f5697d9a7947f0228d8aff", + "rbc_news:title": "Велогонщика отстранили после перепроверки допинг-пробы с Олимпиады-2016", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/08/347105822152088.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Justin Setterfield / Getty Images", + "description": "

Христос Воликакис (в белом)

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/5b26b8089a7947547b4137c6", + "rbc_news:title": "В Бельгии 20 велогонщиков пострадали из-за нарушившей ПДД автомобилистки" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Ефимов рассказал о редевелопменте почти 2 тыс. га бывших промзон в Москве", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66633e709a7947818686c01f", + "attributes": {} + } + ], + "description": { + "content": "Заместитель мэра Москвы по вопросам градостроительной политики и строительства Владимир Ефимов в своем выступлении на ПМЭФ-2024 рассказал, что по программе комплексного развития территорий (КРТ) на разных стадиях проработки и реализации в столице находятся 236 проектов, больше половины из них расположены на участках бывших промзон.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Экономика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/38/347177801323383.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:economics:66633e709a7947818686c01f", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:08:02+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:08:02", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66633e709a7947818686c01f", + "rbc_news:anons": "Заместитель мэра Москвы по вопросам градостроительной политики и строительства Владимир Ефимов в своем выступлении на ПМЭФ-2024 рассказал, что по программе комплексного развития территорий (КРТ) на разных стадиях проработки и реализации в столице находятся 236 проектов, больше половины из них расположены на участках бывших промзон.", + "rbc_news:news_id": "66633e709a7947818686c01f", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717924082", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 13:27:09 +0300", + "rbc_news:newsline": "economics", + "rbc_news:tag": [ + "редевелопмент", + "Владимир Ефимов", + "Москва", + "промзоны" + ], + "rbc_news:full-text": "Заместитель мэра Москвы по вопросам градостроительной политики и строительства Владимир Ефимов в своем выступлении на ПМЭФ-2024 рассказал, что по программе комплексного развития территорий (КРТ) на разных стадиях проработки и реализации в столице находятся 236 проектов, больше половины из них расположены на участках бывших промзон.\n\n«Сегодня в программу вовлечено свыше 3,1 тыс. га земли, из которых 1,8 тыс. расположены на территории бывших промзон, — сообщил Ефимов. — Остальные площади приходятся на неэффективно используемые и незастроенные территории с высоким потенциалом для комплексного развития». Так, по его словам, в рамках реализации 120 проектов на месте бывших промзон планируется создать современные городские пространства со сбалансированной застройкой и рабочими местами.\n\nСтоличная программа КРТ подразумевает создание многофункциональных городских кластеров, где на месте бывших промзон и неэффективно используемых участков возводится современная недвижимость разного назначения с развитой инфраструктурой. Реализация проектов КРТ возможна тремя способами — нынешним собственником участка, городским оператором либо же сторонним девелопером, привлеченным по итогу открытого аукциона.\n\nКак удачный пример редевелопмента вице-мэр привел комплексное развитие части бывшей промзоны «Южный порт». Там в активной стадии реализации находится в совокупности уже четыре проекта КРТ общей площадью более 114 га, на которых построят около 2,5 млн кв. м недвижимости — жилой (в том числе по реновации), промышленной, офисно-деловой и так далее.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/3/38/347177801323383.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "пресс-служба Градостроительного комплекса Москвы", + "rbc_news:description": "

Владимир Ефимов

" + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Умер бывший директор ЗИЛа Валерий Сайкин", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/666569c99a79473c35ec8aa4", + "attributes": {} + } + ], + "description": { + "content": "Валерий Сайкин занимал должность зампредседателя Совета министров РСФСР с апреля по август 1990 года. В 1991-м он баллотировался в мэры Москвы и занял второе место с 16,3%, уступив победу Гавриилу Попову", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/3/67/347179238466673.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666569c99a79473c35ec8aa4", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T12:06:35+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "12:06:35", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/666569c99a79473c35ec8aa4", + "rbc_news:anons": "Валерий Сайкин занимал должность зампредседателя Совета министров РСФСР с апреля по август 1990 года. В 1991-м он баллотировался в мэры Москвы и занял второе место с 16,3%, уступив победу Гавриилу Попову", + "rbc_news:news_id": "666569c99a79473c35ec8aa4", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717923995", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:14:39 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "политик", + "Госдума", + "РСФСР", + "КПРФ", + "ЗИЛ" + ], + "rbc_news:full-text": "Заместитель председателя Совета министров РСФСР, депутат Госдумы третьего созыва и двукратный победитель первенств СССР по классической борьбе Валерий Сайкин умер на 87-м году жизни, сообщила Федерация спортивной борьбы России (ФСБР) на своей странице во «ВКонтакте».\n\n«Федерация спортивной борьбы России, спортсмены и тренеры сборных команд России по вольной, женской и греко-римской борьбе искренне соболезнуют родным и близким Валерия Тимофеевича», — говорится в сообщении.\n\n\nСайкин родился в 1937 году в Москве. Состоял в КПСС с 1966-го. Двукратный чемпион СССР по классической борьбе (среди юношей). С 1981 года — первый заместитель генерального директора, а в 1982–1986-м — генеральный директор ПО «ЗИЛ». По инициативе первого секретаря МГК КПСС Бориса Ельцина в январе 1986 года был избран председателем Мосгорисполкома. В 1990-м стал заместителем председателя Совета министров РСФСР (высший исполнительный и распорядительный и распорядительный орган союзной республики).\n\nВ 1991 году баллотировался в мэры Москвы, занял второе место (16,3%), уступив победу Гавриилу Попову (65,3%). Той же осенью вернулся на ЗИЛ в качестве главного инженера. Депутат Государственной думы третьего созыва от КПРФ в 1999–2003 годах. В последние годы жизни был членом бюро московского городского комитета КПРФ.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/3/67/347179238466673.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Виталий Созинов / ТАСС", + "rbc_news:description": "

Валерий Сайкин, 1987 г.

" + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Сеул возобновит трансляции на границе с КНДР в ответ на «мусорную войну»", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/666566869a7947faaadbf0be", + "attributes": {} + } + ], + "description": { + "content": "Южная Корея возобновит вещание пропагандистских трансляций из громкоговорителей на границе с Северной Кореей, сообщает агентство Yonhap со ссылкой на решение Совета национальной безопасности (СНБ).", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/4/78/347179231116784.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:666566869a7947faaadbf0be", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T11:54:35+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "11:54:35", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/666566869a7947faaadbf0be", + "rbc_news:anons": "Южная Корея возобновит вещание пропагандистских трансляций из громкоговорителей на границе с Северной Кореей, сообщает агентство Yonhap со ссылкой на решение Совета национальной безопасности (СНБ).", + "rbc_news:news_id": "666566869a7947faaadbf0be", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717923275", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 12:01:05 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Южная Корея", + "Северная Корея", + "КНДР", + "воздушные шары" + ], + "rbc_news:full-text": "Южная Корея возобновит вещание пропагандистских трансляций из громкоговорителей на границе с Северной Кореей, сообщает агентство Yonhap со ссылкой на решение Совета национальной безопасности (СНБ).\n\nТеперь жители приграничных районов КНДР смогут услышать K-pop, новости и другие южнокорейские передачи.\n\nС конца мая Пхеньян отправил в сторону Южной Кореи около тысячи воздушных шаров с мусором и навозом. В ответ группа северокорейских беженцев направила с территории Южной Кореи в КНДР воздушные шары с листовками, осуждающими действия соседа.\n\nУчастники заседания СНБ пришли к выводу, что действия КНДР «неприемлемы», и решили принять «соответствующие меры». «Правительство будет сохранять твердую и полную готовность противостоять любым провокациям со стороны Северной Кореи, чтобы обеспечить общественную и национальную безопасность», — подчеркнули в совете.\n\nРешению предшествовала очередная партия воздушных шаров с отходами: накануне Северная Корея запустила около 330 шаров, порядка 80 из них приземлились, пишет агентство.\n\n\n\nЮжная Корея и КНДР начали пропагандистские трансляции с использованием громкоговорителей в 1963 году. Обе стороны приостановили их в 2015-м, накануне переговоров на высшем уровне руководства. Однако в январе 2016 года Сеул возобновил трансляции в ответ на ядерные испытания КНДР. Через громкоговорители транслировались сообщения с критикой коммунистической политической системы Северной Кореи. Это прекратилось в 2018 году.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/4/78/347179231116784.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Korea Pool-Donga Daily / Getty Images" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/6664d6c19a79472f66d34c9c", + "rbc_news:title": "КНДР снова отправила воздушные шары с мусором и навозом в Южную Корею" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/666132c39a79478450a147d7", + "rbc_news:title": "Южная Корея направила в КНДР воздушные шары с листовками", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/99/347176563805998.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Chung Sung-Jun / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/politics/03/06/2024/665d51ec9a7947526f91d930", + "rbc_news:title": "Южная Корея определилась с ответом на шары с навозом из КНДР", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/1/36/347173988907361.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Yonhap / Reuters" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Над Белгородской областью сбили ракету «Нептун» и два дрона", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/66656bfb9a794747ceae4f91", + "attributes": {} + } + ], + "description": { + "content": "Средства противовоздушной обороны сбили управляемую ракету «Нептун-МД» и два беспилотника самолетного типа над Белгородской областью, сообщает Минобороны России.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/8/74/347179234584748.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:66656bfb9a794747ceae4f91", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T11:52:22+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "11:52:22", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/66656bfb9a794747ceae4f91", + "rbc_news:anons": "Средства противовоздушной обороны сбили управляемую ракету «Нептун-МД» и два беспилотника самолетного типа над Белгородской областью, сообщает Минобороны России.", + "rbc_news:news_id": "66656bfb9a794747ceae4f91", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717923142", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 11:57:46 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Белгородская область", + "ракета", + "беспилотники", + "Вячеслав Гладков" + ], + "rbc_news:full-text": "Средства противовоздушной обороны сбили управляемую ракету «Нептун-МД» и два беспилотника самолетного типа над Белгородской областью, сообщает Минобороны России.\n\nАтаки пресекли с 9:30 до 10:30 мск, уточнили в ведомстве.\n\nВ Белгороде и Белгородском районе с 9:50 по 10:15 мск действовал сигнал ракетной опасности.\n\n\n\nБелгородский губернатор Вячеслав Гладков сообщил об уничтожении нескольких воздушных целей над Новооскольским городским округом. По его словам, никто не пострадал. В селе Песчанка выбило окна в частном жилом доме и коммерческом объекте, повреждены два автомобиля. В поселке Прибрежный повреждены окна в подъездах двух многоквартирных домов.\n\nБелгородская область в последние месяцы подвергается интенсивным обстрелам, в регионе периодически объявляют ракетную опасность, сбивают украинские ракеты и дроны.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/8/74/347179234584748.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Николай Гынгазов / ТАСС" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/6665564c9a7947d363ac1ebd", + "rbc_news:title": "В Воронежской области вслед за Белгородом объявили ракетную опасность", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/90/347179180733905.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Сергей Бобылев / ТАСС", + "description": "

Воронеж

" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/666551a39a7947eab562cb15", + "rbc_news:title": "В Белгороде объявили ракетную опасность" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/66652f6b9a7947f9e7a4c4fe", + "rbc_news:title": "Гладков назвал количество установленных в Белгородской области убежищ" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "«Вкусно — и точка» передумала открывать рестораны в Абхазии", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/business/09/06/2024/666563739a7947752bc72774", + "attributes": {} + } + ], + "description": { + "content": "Абхазия на международной арене считается территорией, принадлежащей Грузии, а там работает франшиза McDonald's, с которой «Вкусно — и точка» не хочет конкурировать, объяснил отказ от планов экспансии гендиректор сети Пароев", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Бизнес", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/7/78/347179220201787.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:business:666563739a7947752bc72774", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T11:45:13+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "11:45:13", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/business/09/06/2024/666563739a7947752bc72774", + "rbc_news:anons": "Абхазия на международной арене считается территорией, принадлежащей Грузии, а там работает франшиза McDonald's, с которой «Вкусно — и точка» не хочет конкурировать, объяснил отказ от планов экспансии гендиректор сети Пароев", + "rbc_news:news_id": "666563739a7947752bc72774", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717922713", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 11:54:26 +0300", + "rbc_news:newsline": "business", + "rbc_news:tag": [ + "Абхазия", + "рестораны", + "Вкусно – и точка", + "Макдоналдс", + "Грузия", + "ПМЭФ" + ], + "rbc_news:full-text": "Сеть ресторанов быстрого питания «Вкусно — и точка» больше не планирует выходить на рынок Абхазии, рассказал в интервью «РИА Новости» на ПМЭФ-2024 генеральный директор сети Олег Пароев.\n\n«Мы внимательно изучили рынок Абхазии и пришли к выводу, что не будем там открываться. Причин этому несколько: с одной стороны, Абхазия на международной арене считается территорией, принадлежащей Грузии, а там работает франшиза McDonald's и мы бы не хотели создавать конфликт», — сказал он.\n\nТем не менее, по словам Пароева, вскоре ресторан «Вкусно — и точка» может открыться возле Абхазии — в Сочи или Адлере, что будет «логистически гораздо проще» с точки зрения управления и набора персонала.\n\n\nРоссия признала независимость Абхазии и Южной Осетии в 2008 году.\n\n\nПароев также не стал исключать выход сети «Вкусно — и точка» в другие страны. «На данный момент я не хотел бы говорить про конкретные страны, хотя могу сказать, что конкретные планы уже рассматриваются», — отметил он.\n\n\n\nВладелец «Вкусно — и точка» Александр Говор заявил о планах сети выйти на новые рынки, в том числе в Абхазию, в декабре 2022 года. «Непризнанные республики к нам тоже обращаются. Мы все делаем, чтобы туда зайти, например в Абхазию. Но от нас это не зависит. Новые территории мы пока не рассматриваем, в том числе Крым», — сказал он тогда.\n\nО том, что сеть не планирует выходить в новые регионы, Пароев говорил в начале июня. «Сейчас мы не видим возможности, как мы можем обеспечить стабильность поставок и безопасность наших гостей. Это самая большая проблема», — объяснял он. При этом во всех остальных российских регионах сеть уже присутствует, добавил гендиректор.\n\n\n\n\n«Вкусно — и точка» пришла на смену «Макдоналдсу», который в марте 2022 года приостановил работу в России, а в мае объявил об уходе из страны. Бизнес приобрел сооснователь компании «НефтеХимСервис» Александр Говор, чья компания «Гид» управляла 25 ресторанами американской сети в Сибири. Бизнесмен заявлял, что приобрел бизнес «за символическую плату», но точную сумму разглашать отказался. В соглашении предусмотрен опцион на обратный выкуп в течение 10–15 лет при условии, если Говор будет мастер-франчайзи.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/7/78/347179220201787.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Михаил Гребенщиков / РБК" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/business/07/06/2024/6662de8b9a7947accdbf99a1", + "rbc_news:title": "Попова назвала «Вкусно — и точка» новым «флагманом здорового питания»" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/6662db419a7947a929f85a81", + "rbc_news:title": "Попова назвала сеть «Вкусно — и точка» флагманом здорового питания. Видео" + }, + { + "@url": "https://www.rbc.ru/business/06/06/2024/666189739a79475330035f26", + "rbc_news:title": "Во «Вкусно — и точка» пока не стали планировать выход в Донбасс", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/0/37/347176711656370.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Михаил Гребенщиков / РБК" + } + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "На юге Азербайджана двух пограничников убило ударом молнии", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/rbcfreenews/666563279a7947752bc7276f", + "attributes": {} + } + ], + "description": { + "content": "Два пограничника погибли от удара молнии на юге Азербайджана, сообщила государственная пограничная служба страны.", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Общество", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/64/347179215340646.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:society:666563279a7947752bc7276f", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T11:21:46+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "11:21:46", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/rbcfreenews/666563279a7947752bc7276f", + "rbc_news:anons": "Два пограничника погибли от удара молнии на юге Азербайджана, сообщила государственная пограничная служба страны.", + "rbc_news:news_id": "666563279a7947752bc7276f", + "rbc_news:type": "short_news", + "rbc_news:newsDate_timestamp": "1717921306", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 11:42:59 +0300", + "rbc_news:newsline": "society", + "rbc_news:tag": [ + "Азербайджан", + "молния", + "пограничники" + ], + "rbc_news:full-text": "Два пограничника погибли от удара молнии на юге Азербайджана, сообщила государственная пограничная служба страны. Инцидент произошел утром 9 июня.\n\n«Руководство выражает глубокие соболезнования семьям и близким погибших военнослужащих, желает им терпения», — говорится в публикации. Погибли майор Бахышлы Бахыш Эльшан-оглу и солдат Нагиев Али Эльшан-оглу. Ведомство начало расследование.\n\n\n\nВ конце мая в подмосковной Балашихе также в результате удара молнии погиб мужчина. Телеграм-каналы «112» и Mash сообщили, что ему было 63 года.", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/6/64/347179215340646.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Алексей Куденко / РИА Новости" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/society/06/06/2024/6661b4fa9a79473aff3b6c8f", + "rbc_news:title": "Молния ударила в «Детское посольство» в Москве" + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/665a1af79a794702df7b7d41", + "rbc_news:title": "В Балашихе молния убила мужчину", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/44/347171816956445.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Никита Попов / РБК" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/665a09cb9a794788276d017c", + "rbc_news:title": "В Ростовской области после удара молнии загорелись десятки гектаров леса" + } + ] + } + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "Welt узнала о поиске Макроном союзников для отправки военных на Украину", + "attributes": {} + }, + "link": [ + { + "content": "https://www.rbc.ru/politics/09/06/2024/6665590d9a794782e847f34c", + "attributes": {} + } + ], + "description": { + "content": "Франция направила десяткам западных стран предложение присоединиться к коалиции по отправке военных инструкторов на Украину, но ФРГ выступила против, выяснила Welt. Москва предупреждала, что такие военные будут «законной целью»", + "attributes": {} + }, + "author": { + "content": "Илья Пламенев", + "attributes": {} + }, + "category": [ + { + "content": "Политика", + "attributes": {} + } + ], + "comments": null, + "enclosure": [ + { + "content": null, + "attributes": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/06/347179195427066.jpeg", + "type": "image/jpeg", + "length": "" + } + } + ], + "guid": { + "content": "rssexport.rbc.ru:politics:6665590d9a794782e847f34c", + "attributes": { + "is_perma_link": "false" + } + }, + "pubDate": { + "content": "2024-06-09T11:13:25+03:00", + "attributes": {} + }, + "source": null, + "rbc_news:time": "11:13:25", + "rbc_news:date": "09.06.2024", + "pdalink": "https://www.rbc.ru/politics/09/06/2024/6665590d9a794782e847f34c", + "rbc_news:anons": "Франция направила десяткам западных стран предложение присоединиться к коалиции по отправке военных инструкторов на Украину, но ФРГ выступила против, выяснила Welt. Москва предупреждала, что такие военные будут «законной целью»", + "rbc_news:news_id": "6665590d9a794782e847f34c", + "rbc_news:type": "article", + "rbc_news:newsDate_timestamp": "1717920805", + "rbc_news:newsModifDate": "Sun, 09 Jun 2024 11:49:58 +0300", + "rbc_news:newsline": "politics", + "rbc_news:tag": [ + "Эмманюэль Макрон", + "военные инструкторы", + "Военная операция на Украине", + "коалиция", + "Германия", + "Франция" + ], + "rbc_news:full-text": "Французский президент Эмманюэль Макрон пытается создать коалицию западных стран для совместной отправки военных инструкторов на Украину, сообщает немецкая Welt am Sonntag со ссылкой на дипломатические источники.\n\nПо данным газеты, на прошлой неделе начальник Главного штаба ВС Франции Тьерри Буркхард направил письмо десяткам западных стран, в том числе США, Великобритании, Польше, Нидерландам, Дании и Швеции, в котором пригласил их принять участие в учебной миссии на Украине в рамках многонациональной «коалиции желающих». В то же время в ФРГ оно направлено не было: источники Welt уточнили, что это связано с демонстративным нежеланием правительства Германии направлять свои войска на Украину в любом виде.\n\nСобеседники газеты также рассказали, что миссия на Украине с участием военных инструкторов, по плану Макрона, может быть реализована под эгидой уже существующей учебной миссии ЕС в Украине EUMAM. Однако, чтобы это произошло, необходимо изменить мандат миссии, напоминает Welt. В то же время, отмечает газета, такое вполне возможно: летом параметры EUMAM должны быть пересмотрены, а в ноябре ее необходимо будет продлить.\n\nИсточники отмечают, что пока инициатива Парижа сталкивается с сопротивлением в большинстве стран. Так, правительства Германии, Италии и Испании выразили опасения, что отправка военных инструкторов может создать значительный риск эскалации и «еще глубже» втянуть Запад в конфликт. Венгрия, в свою очередь, еще в конце мая заявила, что у Украины нет никаких шансов одержать верх над Россией.\n\n\n\nДискуссия о возможном вводе войск западных стран на Украину началась в конце февраля, когда такой вариант впервые не исключил Макрон. В марте он допустил и проведение наземной операции против российской армии. Условиями отправки западных войск на Украину будут соответствующий запрос Киева и прорыв фронта, уточнял французский лидер в начале мая.\n\nВ конце мая главком ВСУ Александр Сырский подписал документы, «которые позволят первым французским инструкторам вскоре посетить украинские учебные центры и ознакомиться с их инфраструктурой и персоналом». А спустя еще несколько дней депутат Верховной рады Алексей Гончаренко со ссылкой на собственный источники заявил, что первая группа инструкторов уже направилась в страну.\n\nРоссийский президент Владимир Путин на встрече с представителями международных информагентств 5 июня заявил, что западные военные инструкторы и советники уже присутствуют на Украине и, «к сожалению для них, несут потери». В Кремле предупреждали, что иностранные военные, которые тренируют ВСУ на территории страны, не защищены от ударов, у них «нет никаких иммунитетов».", + "rbc_news:image": { + "rbc_news:url": "https://s0.rbk.ru/v6_top_pics/media/img/6/06/347179195427066.jpeg", + "rbc_news:type": "image/jpeg", + "rbc_news:size_nick": "original", + "rbc_news:copyright": "copyright", + "rbc_news:source": "Clemens Bilan / Getty Images", + "rbc_news:description": "

Эмманюэль Макрон

" + }, + "rbc_news:related_links": { + "link": [ + { + "@url": "https://www.rbc.ru/rbcfreenews/666352519a79470b2983ae9b", + "rbc_news:title": "Макрон заявил о начале обучения Францией украинских пилотов", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/6/11/347177862057116.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Joe Raedle / Getty Images" + } + }, + { + "@url": "https://www.rbc.ru/rbcfreenews/666198cf9a79475d8b128eec", + "rbc_news:title": "Зеленский прибыл во Францию на юбилей высадки в Нормандии", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/34/347176768866345.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Piroschka van de Wouw / File Photo / Reuters", + "description": "

Владимир Зеленский

" + } + }, + { + "@url": "https://www.rbc.ru/politics/09/06/2024/6664dbda9a7947fc7b6e8869", + "rbc_news:title": "В Чехии исключили отправку своих инструкторов на Украину", + "rbc_news:thumbnail": { + "url": "https://s0.rbk.ru/v6_top_pics/media/img/5/09/347178878144095.jpeg", + "type": "image/jpeg", + "size_nick": "original", + "copyright": "limited", + "source": "Oleksandr Ratushniak / Reuters" + } + } + ] + } + }, + "attributes": {} + } + ], + "language": { + "content": "ru", + "attributes": {} + }, + "copyright": null, + "managingEditor": null, + "webMaster": null, + "pubDate": null, + "lastBuildDate": null, + "category": [], + "generator": null, + "docs": null, + "cloud": null, + "ttl": { + "content": 30, + "attributes": {} + }, + "image": { + "content": { + "url": { + "content": "http://pics.rbc.ru/img/fp_v4/skin/img/v6-logo.png", + "attributes": {} + }, + "title": { + "content": "Главные новости :: www.rbc.ru", + "attributes": {} + }, + "link": { + "content": "https://www.rbc.ru", + "attributes": {} + }, + "width": null, + "height": null, + "description": null + }, + "attributes": {} + }, + "rating": null, + "textInput": null, + "skipHours": null, + "skipDays": null + }, + "attributes": {} + }, + "@xmlns:rbc_news": "https://www.rbc.ru" +} diff --git a/tests/samples/rss/rss_0_91/data.xml b/tests/samples/rss/rss_0_91/data.xml new file mode 100644 index 0000000..ed3ce89 --- /dev/null +++ b/tests/samples/rss/rss_0_91/data.xml @@ -0,0 +1,40 @@ + + + + Scripting Nonsense + http://scripting-nonsense.example.com + Daily nonsense about scripting + en-us + Copyright 1999, Example Corp. + editor@scripting-nonsense.example.com + webmaster@scripting-nonsense.example.com + + Scripting Nonsense + http://scripting-nonsense.example.com/logo.gif + http://scripting-nonsense.example.com + 88 + 31 + The finest nonsense on the web + + + 0 + 1 + 2 + + + Sunday + + + A title-only item is fine in 0.91 + http://scripting-nonsense.example.com/1999/06/07#a1 + + + A description-only item is fine too + + + An item with everything 0.91 allows + http://scripting-nonsense.example.com/1999/06/07#a2 + Some detailed description of the nonsense at hand + + + diff --git a/tests/samples/rss/rss_0_91/result.json b/tests/samples/rss/rss_0_91/result.json new file mode 100644 index 0000000..2c89a22 --- /dev/null +++ b/tests/samples/rss/rss_0_91/result.json @@ -0,0 +1,176 @@ +{ + "@version": { + "content": "0.91", + "attributes": {} + }, + "channel": { + "content": { + "title": { + "content": "Scripting Nonsense", + "attributes": {} + }, + "link": { + "content": "http://scripting-nonsense.example.com", + "attributes": {} + }, + "description": { + "content": "Daily nonsense about scripting", + "attributes": {} + }, + "item": [ + { + "content": { + "title": { + "content": "A title-only item is fine in 0.91", + "attributes": {} + }, + "link": [ + { + "content": "http://scripting-nonsense.example.com/1999/06/07#a1", + "attributes": {} + } + ], + "description": null, + "author": null, + "category": [], + "comments": null, + "enclosure": [], + "guid": null, + "pubDate": null, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": null, + "link": [], + "description": { + "content": "A description-only item is fine too", + "attributes": {} + }, + "author": null, + "category": [], + "comments": null, + "enclosure": [], + "guid": null, + "pubDate": null, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "An item with everything 0.91 allows", + "attributes": {} + }, + "link": [ + { + "content": "http://scripting-nonsense.example.com/1999/06/07#a2", + "attributes": {} + } + ], + "description": { + "content": "Some detailed description of the nonsense at hand", + "attributes": {} + }, + "author": null, + "category": [], + "comments": null, + "enclosure": [], + "guid": null, + "pubDate": null, + "source": null + }, + "attributes": {} + } + ], + "language": { + "content": "en-us", + "attributes": {} + }, + "copyright": { + "content": "Copyright 1999, Example Corp.", + "attributes": {} + }, + "managingEditor": { + "content": "editor@scripting-nonsense.example.com", + "attributes": {} + }, + "webMaster": { + "content": "webmaster@scripting-nonsense.example.com", + "attributes": {} + }, + "pubDate": null, + "lastBuildDate": null, + "category": [], + "generator": null, + "docs": null, + "cloud": null, + "ttl": null, + "image": { + "content": { + "url": { + "content": "http://scripting-nonsense.example.com/logo.gif", + "attributes": {} + }, + "title": { + "content": "Scripting Nonsense", + "attributes": {} + }, + "link": { + "content": "http://scripting-nonsense.example.com", + "attributes": {} + }, + "width": { + "content": 88, + "attributes": {} + }, + "height": { + "content": 31, + "attributes": {} + }, + "description": { + "content": "The finest nonsense on the web", + "attributes": {} + } + }, + "attributes": {} + }, + "rating": null, + "textInput": null, + "skipHours": { + "content": { + "hour": [ + { + "content": 0, + "attributes": {} + }, + { + "content": 1, + "attributes": {} + }, + { + "content": 2, + "attributes": {} + } + ] + }, + "attributes": {} + }, + "skipDays": { + "content": { + "day": [ + { + "content": "Sunday", + "attributes": {} + } + ] + }, + "attributes": {} + } + }, + "attributes": {} + } +} diff --git a/tests/samples/rss_2/data.xml b/tests/samples/rss/rss_2/data.xml similarity index 100% rename from tests/samples/rss_2/data.xml rename to tests/samples/rss/rss_2/data.xml diff --git a/tests/samples/rss/rss_2/result.json b/tests/samples/rss/rss_2/result.json new file mode 100644 index 0000000..2354386 --- /dev/null +++ b/tests/samples/rss/rss_2/result.json @@ -0,0 +1,451 @@ +{ + "@version": { + "content": "2.0", + "attributes": {} + }, + "channel": { + "content": { + "title": { + "content": "FeedForAll Sample Feed", + "attributes": {} + }, + "link": { + "content": "http://www.feedforall.com/industry-solutions.htm", + "attributes": {} + }, + "description": { + "content": "RSS is a fascinating technology. The uses for RSS are expanding daily. Take a closer look at how various industries are using the benefits of RSS in their businesses.", + "attributes": {} + }, + "item": [ + { + "content": { + "title": { + "content": "RSS Solutions for Restaurants", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/restaurant.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Restaurant's communicate with customers. Let your customers know the latest specials or events.
\n
\nRSS feed uses include:
\nDaily Specials
\nEntertainment
\nCalendar of Events
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:11-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Schools and Colleges", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/schools.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Educational Institutions communicate with students about school wide activities, events, and schedules.
\n
\nRSS feed uses include:
\nHomework Assignments
\nSchool Cancellations
\nCalendar of Events
\nSports Scores
\nClubs/Organization Meetings
\nLunches Menus
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:09-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Computer Service Companies", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/computer-service.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Computer Service Companies communicate with clients about cyber security and related issues.
\n
\nUses include:
\nCyber Security Alerts
\nSpecials
\nJob Postings
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:07-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Governments", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/government.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Governments communicate with the general public about positions on various issues, and keep the community aware of changes in important legislative issues.
\n

\nRSS uses Include:
\nLegislative Calendar
\nVotes
\nBulletins
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:05-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Politicians", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/politics.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Politicians communicate with the general public about positions on various issues, and keep the community notified of their schedule.
\n
\nUses Include:
\nBlogs
\nSpeaking Engagements
\nStatements
\n
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:03-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Meteorologists", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/weather.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Meteorologists communicate with the general public about storm warnings and weather alerts, in specific regions. Using RSS meteorologists are able to quickly disseminate urgent and life threatening weather warnings.
\n
\nUses Include:
\nWeather Alerts
\nPlotting Storms
\nSchool Cancellations
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:01-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Realtors & Real Estate Firms", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/real-estate.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Realtors and Real Estate companies communicate with clients informing them of newly available properties, and open house announcements. RSS helps to reach a targeted audience and spread the word in an inexpensive, professional manner.
\n

\nFeeds can be used for:
\nOpen House Dates
\nNew Properties For Sale
\nMortgage Rates
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:08:59-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Banks / Mortgage Companies", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/banks.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Banks, Credit Unions and Mortgage companies communicate with the general public about rate changes in a prompt and professional manner.
\n
\nUses include:
\nMortgage Rates
\nForeign Exchange Rates
\nBank Rates
\nSpecials
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:08:57-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Law Enforcement", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/law-enforcement.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Law Enforcement Professionals communicate with the general public and other agencies in a prompt and efficient manner. Using RSS police are able to quickly disseminate urgent and life threatening information.
\n
\nUses include:
\nAmber Alerts
\nSex Offender Community Notification
\nWeather Alerts
\nScheduling
\nSecurity Alerts
\nPolice Report
\nMeetings
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:08:56-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + } + ], + "language": { + "content": "en-us", + "attributes": {} + }, + "copyright": { + "content": "Copyright 2004 NotePage, Inc.", + "attributes": {} + }, + "managingEditor": { + "content": "marketing@feedforall.com", + "attributes": {} + }, + "webMaster": { + "content": "webmaster@feedforall.com", + "attributes": {} + }, + "pubDate": { + "content": "2004-10-19T13:38:55-04:00", + "attributes": {} + }, + "lastBuildDate": { + "content": "2004-10-19T13:39:14-04:00", + "attributes": {} + }, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "generator": { + "content": "FeedForAll Beta1 (0.0.1.8)", + "attributes": {} + }, + "docs": { + "content": "http://blogs.law.harvard.edu/tech/rss", + "attributes": {} + }, + "cloud": null, + "ttl": null, + "image": { + "content": { + "url": { + "content": "http://www.feedforall.com/ffalogo48x48.gif", + "attributes": {} + }, + "title": { + "content": "FeedForAll Sample Feed", + "attributes": {} + }, + "link": { + "content": "http://www.feedforall.com/industry-solutions.htm", + "attributes": {} + }, + "width": { + "content": 48, + "attributes": {} + }, + "height": { + "content": 48, + "attributes": {} + }, + "description": { + "content": "FeedForAll Sample Feed", + "attributes": {} + } + }, + "attributes": {} + }, + "rating": null, + "textInput": null, + "skipHours": null, + "skipDays": null + }, + "attributes": {} + } +} diff --git a/tests/samples/rss_2_no_category_attr/data.xml b/tests/samples/rss/rss_2_no_category_attr/data.xml similarity index 100% rename from tests/samples/rss_2_no_category_attr/data.xml rename to tests/samples/rss/rss_2_no_category_attr/data.xml diff --git a/tests/samples/rss/rss_2_no_category_attr/result.json b/tests/samples/rss/rss_2_no_category_attr/result.json new file mode 100644 index 0000000..6bd57dd --- /dev/null +++ b/tests/samples/rss/rss_2_no_category_attr/result.json @@ -0,0 +1,447 @@ +{ + "@version": { + "content": "2.0", + "attributes": {} + }, + "channel": { + "content": { + "title": { + "content": "FeedForAll Sample Feed", + "attributes": {} + }, + "link": { + "content": "http://www.feedforall.com/industry-solutions.htm", + "attributes": {} + }, + "description": { + "content": "RSS is a fascinating technology. The uses for RSS are expanding daily. Take a closer look at how various industries are using the benefits of RSS in their businesses.", + "attributes": {} + }, + "item": [ + { + "content": { + "title": { + "content": "RSS Solutions for Restaurants", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/restaurant.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Restaurant's communicate with customers. Let your customers know the latest specials or events.
\n
\nRSS feed uses include:
\nDaily Specials
\nEntertainment
\nCalendar of Events
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": {} + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:11-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Schools and Colleges", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/schools.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Educational Institutions communicate with students about school wide activities, events, and schedules.
\n
\nRSS feed uses include:
\nHomework Assignments
\nSchool Cancellations
\nCalendar of Events
\nSports Scores
\nClubs/Organization Meetings
\nLunches Menus
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:09-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Computer Service Companies", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/computer-service.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Computer Service Companies communicate with clients about cyber security and related issues.
\n
\nUses include:
\nCyber Security Alerts
\nSpecials
\nJob Postings
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:07-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Governments", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/government.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Governments communicate with the general public about positions on various issues, and keep the community aware of changes in important legislative issues.
\n

\nRSS uses Include:
\nLegislative Calendar
\nVotes
\nBulletins
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:05-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Politicians", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/politics.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Politicians communicate with the general public about positions on various issues, and keep the community notified of their schedule.
\n
\nUses Include:
\nBlogs
\nSpeaking Engagements
\nStatements
\n
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:03-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Meteorologists", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/weather.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Meteorologists communicate with the general public about storm warnings and weather alerts, in specific regions. Using RSS meteorologists are able to quickly disseminate urgent and life threatening weather warnings.
\n
\nUses Include:
\nWeather Alerts
\nPlotting Storms
\nSchool Cancellations
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:01-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Realtors & Real Estate Firms", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/real-estate.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Realtors and Real Estate companies communicate with clients informing them of newly available properties, and open house announcements. RSS helps to reach a targeted audience and spread the word in an inexpensive, professional manner.
\n

\nFeeds can be used for:
\nOpen House Dates
\nNew Properties For Sale
\nMortgage Rates
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:08:59-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Banks / Mortgage Companies", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/banks.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Banks, Credit Unions and Mortgage companies communicate with the general public about rate changes in a prompt and professional manner.
\n
\nUses include:
\nMortgage Rates
\nForeign Exchange Rates
\nBank Rates
\nSpecials
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:08:57-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + }, + { + "content": { + "title": { + "content": "RSS Solutions for Law Enforcement", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/law-enforcement.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Law Enforcement Professionals communicate with the general public and other agencies in a prompt and efficient manner. Using RSS police are able to quickly disseminate urgent and life threatening information.
\n
\nUses include:
\nAmber Alerts
\nSex Offender Community Notification
\nWeather Alerts
\nScheduling
\nSecurity Alerts
\nPolice Report
\nMeetings
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:08:56-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + } + ], + "language": { + "content": "en-us", + "attributes": {} + }, + "copyright": { + "content": "Copyright 2004 NotePage, Inc.", + "attributes": {} + }, + "managingEditor": { + "content": "marketing@feedforall.com", + "attributes": {} + }, + "webMaster": { + "content": "webmaster@feedforall.com", + "attributes": {} + }, + "pubDate": { + "content": "2004-10-19T13:38:55-04:00", + "attributes": {} + }, + "lastBuildDate": { + "content": "2004-10-19T13:39:14-04:00", + "attributes": {} + }, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": {} + } + ], + "generator": { + "content": "FeedForAll Beta1 (0.0.1.8)", + "attributes": {} + }, + "docs": { + "content": "http://blogs.law.harvard.edu/tech/rss", + "attributes": {} + }, + "cloud": null, + "ttl": null, + "image": { + "content": { + "url": { + "content": "http://www.feedforall.com/ffalogo48x48.gif", + "attributes": {} + }, + "title": { + "content": "FeedForAll Sample Feed", + "attributes": {} + }, + "link": { + "content": "http://www.feedforall.com/industry-solutions.htm", + "attributes": {} + }, + "width": { + "content": 48, + "attributes": {} + }, + "height": { + "content": 48, + "attributes": {} + }, + "description": { + "content": "FeedForAll Sample Feed", + "attributes": {} + } + }, + "attributes": {} + }, + "rating": null, + "textInput": null, + "skipHours": null, + "skipDays": null + }, + "attributes": {} + } +} diff --git a/tests/samples/rss_2_with_1_item/data.xml b/tests/samples/rss/rss_2_with_1_item/data.xml similarity index 100% rename from tests/samples/rss_2_with_1_item/data.xml rename to tests/samples/rss/rss_2_with_1_item/data.xml diff --git a/tests/samples/rss/rss_2_with_1_item/result.json b/tests/samples/rss/rss_2_with_1_item/result.json new file mode 100644 index 0000000..25b6a92 --- /dev/null +++ b/tests/samples/rss/rss_2_with_1_item/result.json @@ -0,0 +1,139 @@ +{ + "@version": { + "content": "2.0", + "attributes": {} + }, + "channel": { + "content": { + "title": { + "content": "FeedForAll Sample Feed", + "attributes": {} + }, + "link": { + "content": "http://www.feedforall.com/industry-solutions.htm", + "attributes": {} + }, + "description": { + "content": "RSS is a fascinating technology. The uses for RSS are expanding daily. Take a closer look at how various industries are using the benefits of RSS in their businesses.", + "attributes": {} + }, + "item": [ + { + "content": { + "title": { + "content": "RSS Solutions for Restaurants", + "attributes": {} + }, + "link": [ + { + "content": "http://www.feedforall.com/restaurant.htm", + "attributes": {} + } + ], + "description": { + "content": "FeedForAll helps Restaurant's communicate with customers. Let your customers know the latest specials or events.
\n
\nRSS feed uses include:
\nDaily Specials
\nEntertainment
\nCalendar of Events
", + "attributes": {} + }, + "author": null, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "comments": { + "content": "http://www.feedforall.com/forum", + "attributes": {} + }, + "enclosure": [], + "guid": null, + "pubDate": { + "content": "2004-10-19T11:09:11-04:00", + "attributes": {} + }, + "source": null + }, + "attributes": {} + } + ], + "language": { + "content": "en-us", + "attributes": {} + }, + "copyright": { + "content": "Copyright 2004 NotePage, Inc.", + "attributes": {} + }, + "managingEditor": { + "content": "marketing@feedforall.com", + "attributes": {} + }, + "webMaster": { + "content": "webmaster@feedforall.com", + "attributes": {} + }, + "pubDate": { + "content": "2004-10-19T13:38:55-04:00", + "attributes": {} + }, + "lastBuildDate": { + "content": "2004-10-19T13:39:14-04:00", + "attributes": {} + }, + "category": [ + { + "content": "Computers/Software/Internet/Site Management/Content Management", + "attributes": { + "domain": "www.dmoz.com" + } + } + ], + "generator": { + "content": "FeedForAll Beta1 (0.0.1.8)", + "attributes": {} + }, + "docs": { + "content": "http://blogs.law.harvard.edu/tech/rss", + "attributes": {} + }, + "cloud": null, + "ttl": null, + "image": { + "content": { + "url": { + "content": "http://www.feedforall.com/ffalogo48x48.gif", + "attributes": {} + }, + "title": { + "content": "FeedForAll Sample Feed", + "attributes": {} + }, + "link": { + "content": "http://www.feedforall.com/industry-solutions.htm", + "attributes": {} + }, + "width": { + "content": 48, + "attributes": {} + }, + "height": { + "content": 48, + "attributes": {} + }, + "description": { + "content": "FeedForAll Sample Feed", + "attributes": {} + } + }, + "attributes": {} + }, + "rating": null, + "textInput": null, + "skipHours": null, + "skipDays": null + }, + "attributes": {} + } +} diff --git a/tests/samples/rss_2/result.pkl b/tests/samples/rss_2/result.pkl deleted file mode 100644 index 64c108ecb1086df1ae96a8de582cdd47b759c7e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7370 zcmds+&u$yX9mf^el4YfdfwlP;11{ieCGT6vwyhy zmw&u7=RaQ!Z1Tl@b|MPOngl zdFe!MhtG$%M@0*~aH%%KopLGWPV}^qX#PC3DK}d>@1sLw8*SZn18zj)5e79%xJDbj zv8P<|p2#tcMk`EbtZ10de>E%@5}k9YhUE_igF%$!`c)L`d}s?^F5(Ndr3qt><76Z)@`)%h*Pv zvzdylZ^uS|-!j^`LPYs)oj|nFF`G;NA1$1ApA}YECb8E=BG$w2a&x1LbH@0YHow00-u8AApIe)sa+w{o zcHOP7yhWrq96cQ|^F=M536*febeC=Uaj`)3iQ`pnGi++%x3(rUeRC(h@jdaFzI71{ z)3=`w(|2~#n^mJ3v^DRI)0V!wlimsqmL~yg$Ensh6|NH2(pe^YAlz?9x?JzU&u24c zL-R4f(hvPuU+%s(H#Y`V0BS2Kzz@jd8GEEG;ICMpfXg}%07w|`=mPZkall<77>}Ji zk`Ah|QjxUcr;j=!DKhb0!6K}-w{LBJ2B-(x9I^-2%AQBb2A0BRu@+ac$iTI~rZXuc zy4!tiu!wf7aT_Zjv!Y`gyQasLeC7LKPla$&^{NU_3Wb$el&S(nmmStmX27~K32Upe z4XiE9k&Fe1&HLEKNn`=!ELjI?-oUKd(8(mb<-aDtojvjtUCiVdx$$vF7-mIW80n5Z zs78<`CQMpefYyM8$LQHN%U?}t%lF%^`+bl>_-afmP4rzp)*ZH|an(FummQ{$XTUVY zwEjlJwF$TTtl*tvRC}XVliYmy;WW(~8)uRgGLSu?1e^{AUm}xnC3t;pA#vmeXvL1W zks4YMfC*}MC`8}eSMW@Bnb;z4iR>J?VbF0HQYLd!Aovdq(k?BD){z(^Z*v2-(A!~H zeXlk2Kv&P3;z#G2V$w@8#ZSh%uI;Vw^tT$e-+y~pz_tKFd+GJ;m){=2J)Pl@;TEY0 zo4Y()!1|$;odV$N(*PVB<^K$z(oS|Ilno4mYzf_~Z1RN2oex&+<2}rDFYp6Dj~_zA z*p}*5?tiUz4r1zEdJo#=H<+hli81evO&ZLn!U=8QIOFQq5yZFh>jr}30L>J{#5BZC zk8=Ua5J8xnLAR8*PvnJGLo@j8uxKkJVm@~0LK_W)2AUYLXO>u?1?Q)h$VdTsClkzR z_WMG{hlmjp=%&b}qR6E%kXz@?a3;H`mKrQo(D0*?>*-t&+S)SzF6yJXY6E?D{o`4oNi+%XzTg?Q%d%wzqOgx!2xoDE+Y&o?)?$A!TP|_F z*(!y?PE5i1uhVcIjZ8d_k4?U#_ctdIk1MZFD#Y!8tw#!&lM*U10EGMq@v0}YZ*(7M z`}l{Jr>^sO+O2hjPau#{ML*N9Ov5q)poH-7J$$jKS0!I^GFm+=4_&xCs0b&>U zmI}rAFJ7W|3!A^fFm6{Mr18;LPKg==!cWE4HNqa+Dn$0*$stAx09|%)hb%_ND!(B> z-=%eZ=GXNpHa}3mBL-}5v;hn$2iC?-_k%ozr$XY+_I>rseibed_&nsu`LG5X?GVKy zQv;v(W?zI3bu#4&EKj=vND28tS6Iu4L^}3Nd2f_h8_i;L9Vlfqc_n5#w}$TVn3Kwh z&AcCEc{-+}RM7=Oq|rPBPt*l?6Nu^A*2^$_KrKXIwB8Cms#<53F1N!9N&eLghxOe_ zNq)iyaKLc&gG8NG;nmsjWqhzIPFhaE@bAFzR3g$t73A;CxTGK>DVvg+oCY2VEx9xd z0;6Jxub0Si@I)6b28Hgd7RM7WgM4mM+W*132!_EYd?p-=^D~~cDnH06l`!U3w8cxd z+wCI1iE>Cn6$kEkH#{WT0@|ltl2C?Z7NyI`QhD{Ano6M90qfou6h(%5b+T&4y%6PM zhUl}vHdpE8d7)N|I(>aT*8O86d#S5_z1gY@Lc$!A7kg;Df&^h41;Wauf#@9M+#6E` zxEkvHH~mmQM9GUE8oS+$n$cQx_k(J!R}&rA zZ%^vXaEIcw2zNY6xk0ufiGWtai8yT-TOa*A-k=op*5rzF8NK_E`3Mr^jLR#9soorw zznCo~-7d#q`p(^#ckV_#*{#mI%5c9mHvKlw`v?T6q@FsxJE<1hoIQk830`fO<5gR~ zi#d_2avnKomxfM0Z-(PvK&(3=x+<*l;N`h93NIeY1sFu!L+r_t6EEF?RLD@$lRcFE z1Gt99;_147O87Az>Wu!my}!5*&_d?_JFd1~#NWsiNF?J~m)n>kDevIod=&+k#}WGi zi7fbq)mtmnCaHw;34ejACiJofwfG@Kl+mj+C|0KRA>$6d@8BfG751L=nR+OEoJV@z fL6)p5k>Z3$?PsH^6hnvVz^_FnH5_x;)FS#X{6J4` diff --git a/tests/samples/rss_2_no_category_attr/result.pkl b/tests/samples/rss_2_no_category_attr/result.pkl deleted file mode 100644 index 3bcaa196c0ad09fe01bbb137278939b86d94c4d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7320 zcmds+-Etg98HSazCCds)D4?hiNTqQ=u|b+$1&7!wp^Ckch#gx}X=BKVOVvBmyVKT8 z_tM>?m0U#?C&0;Yl;r|A;TE_N?tr`C1m3TEX0_7pDp3e0Qk7J@t^V()-|wgYaQ&}; z`O%#J`Tg*AdE-bJE43Q>FR@lmC^syxy&pA)7p79alRDR5Z!`vjLDUr@>1xBXEJCdYJ{Eas zS1t(^XWACViKD`bL+F7}JgL4wKFy?s`Q6}EPt?Z%G1-)!DxYisMx`|E3KY_)l} z*{ph7u(~j@KnL?qx~y*f_I|s~+Bz#7_SG_s&-R6NyfB<5>{n3EXHp%WZp-c2^T%AP zwo&S;h4Av#MA+EKK8;b$`rjL!&26lGTbl=2#x@$A%~WK4J2v`zmeIx)O48A=fpjU0 z3+r@_1BuuZ;n)e*bXvv^l^!sc3YMXPb}H#fRCPK=*v^Q&9$Zf`g7b8GWsF0&KXuDkV>w+INwrJ^HdzNp1Bp%QMG z?y@aEE*6MBalFcHhD|N}*47}VukWO9e3RIf3yID#sbTpo!Ys-4O9E_Y3!Z-OG8m?B zJ|CuU?W8}f8qJ`sd4HU?^qrlQg$B!$fVJaPYn%#K32W&r6Fm^_w z#9^f(X~j<;bwpBR;_HG%SZ#0L+WZ7i541UC53H3vkCF{6h0S6uu40jaYky5=Qbu&Q z``Ta;?O5YBRzPM&$2N9Nk1P4o_rabD;iT$S6`m9dE3qh51&XdZtRK#Rb!8IPR%HTM zTbLsm3lN+4v5k|+0?1ji4%ECIS+${)Np{PBO@KRlksF{DJK{!a zXh8rbsNJCueQ#gEGudTgi<}~|bL4G7$6-jB%t?XZKQKtUv?N+bVvM}a4cJ0&hhg=- z*3cKadfpU2y3iDpUXm$(GS+o%Z+)k~(Xjpg)58L`1rXXRuV=sb>Im-X42KN2NKM$> z<=Fz(53TGJ0AHO3;MgetX8@IUvMZr%U=U#YqfI_Q}5Dy(5}A0JQGWdd3S8mU_KR2XamO?SHF%RzLH-z5F7_+rXVJ!A$E40 zOHhUg!sHCPrM!J2FSHt(!Ec8}TOkqiu|pTyXdpDu#E3n!!~!iiKeI$e3dlQ|U{15& z6*4|VjF3P#MJ^RZE`@>II&X$E*+sR~V5x$JAB|j3=aSIYmif0~?^rO&9?HE;JF>v6 zP3x_Q4leCEXYxuL=zHrQ%nD7SNqF}K&#+yVEh7+xecVDgixb?I*unD_`_62+#QA2c z6bd^r1?Rs_!+A6^@i;y<`HtS-oJ2gXygsQAw*$5wDPT@YsKfve@*~8np3J_{eW2~* z7a9QH6iZ>>L9S97-hYs@$nzZnC(w*_#3|#jFg7`0T1F(&v1iJAqr}>17NhGxDWl0NG1Iv z{UFQJF&(9fE)gP)<{5aRF2I{WOwYEC!|(yM5P{KpEA*&pomslt4l5-2mopsJA5Tj1 z6Fz_ghN~YW>bwfC&W11JgH>_baten30ETB0kshibf9J*}1sO@%l+5Hb@JMLMrC|^l z6+?WzM2>?ex^OWlbZ50Vo_ZPNbCc5k58g#E3_jsA;aFUp@vK$(K~AZJF}I>Ej@fRv zi~J_aAqiC+xZ~aMkZ22NpLIz>8IoC)E+b3j)pu$tfno=&dtXo#8S2%^su}k}l#3an z&jQr&!z9aw)`QUGlnlHYAP40)=RC# z@QbSCCY2;2a~wsF|AU{$AN}B!xliZ*Jlr4t5x;-JZ%E3&kzsBJokC{G*mJ;(*U)h_ z)O)Y{p}vih7vI633;X+K)6aPIxEXQ`@TRKmduuGi^-wsz#%?#GX0#UFyHhRcdZOd{ zk)%!lcPJc-aFwH!8zd!?2n02ph|`9#^$E}8p`@V4k!#Ln^u!_a5erBSm)8nYy#tj$ zo2?h!F2`W{?!Du?_oANcR>xW;vcEqz{Wj10@b{^to;p2|R10m+9zv=FFBs-z)z(ig zr>a!WBh~EE(8;IGaLNk^X-D8zg;lz{yl_V0eW6@{-NSXmo-8?W><*+tB9gw@L)kxo z2WTw5-SpoQeu6J`;{L+kUu=Mt+u3NYSM_1Fa|zmTCF|yp zC}~03y4t)OM0GW!)@mL*3kJWtYA0b-N)TR|ZF-9Vw@{e+hm~ZTI?I%WnI0q zSS*6HP%nc-6}54++Qk=d>+vGQ>LX@tc^ZaZF#||5rKu2JJ;Ya6G!9)g1epkBoNl02 zTe;&YX0Thjc$Q)oy|)(*?vT+v@?B!d-6%RHqtR(}?{qXGN6~Q6m(aykKYFtFT; z-g^IHIweyjoW*(@g7L`(7)za|9+vJj&Ng7SON3T$r(hDzOCO^{ z{l{56KF8e8mHt`?au&v82BI`;(Qg~#3+E*FJtGTl8A+V6s=!VKZ$ zSO!F(fw9DtkZ>vt!K~oYcOW>6^?2AkeB!?P#2GMzW362Zr|ow>KFhGr7+)xT|KQ{E z^9Zkl@gvH`l1zKIjq+nZDHdDrTTB|!gbI);)kJ0FaT6C4M4#ARX~%6*T~k^d4nbw^ zaJss}3_u>azVLgADwmq)%ywV*C%vF;^>bz6do;d7p2#HV2WJ8Cte^&Qb!InF(h45@ zbKqJFw%-ost{*GQ3#hBr>+5#dDv8^6>S^-p;JjCVUoHw2=Z#g2<~ZG_oZU4+~3H4z@NNB`=E-T1#YcP|GC znp&iewn87S&Fn+4bVLrLAPPpo$!A@PTd7L=MgOE*lvt?T1cELCM)f7tX#lAUEhH?| zrtkFtm)-dV-?UcvHRZ+MQ{h={)9H@ms?V=qz$~K}OdX$G9iId_&$#S=4rYo55;?J0bhx4G^_EQf*@g zxXiI8Lkm}S#wD_~Kbbi%XAC*O;AyWprF4l0_9pqVvW4&;Q|&YO%@YYXgp!P!gv$mi z@y(UU9{DmLFOZQMl4;l#webf-;HsoD#k5H3Nd>j&9MFlUe9%NFH7C>(hKz<2!not a feed") + + +class TestUniversalParse: + def test_rss_2(self): + feed = parse(RSS_2) + + assert isinstance(feed, RSS) + assert feed.version.content == "2.0" + + def test_rss_0_91(self): + feed = parse(RSS_0_91) + + assert isinstance(feed, RSS) + assert feed.version.content == "0.91" + + def test_atom(self): + feed = parse(ATOM) + + assert isinstance(feed, Atom) + + def test_rdf(self): + feed = parse(RDF_1_0) + + assert isinstance(feed, RDF) + assert feed.channel.content.title.content == "Meerkat" + assert len(feed.items) == 2 + + def test_unknown_root_raises_with_supported_types_listed(self): + with pytest.raises(UnknownFeedTypeError, match="rdf:RDF"): + parse("not a feed") + + def test_parser_override(self): + feed = parse(RSS_2, parsers={FeedType.RSS: PodcastParser}) + + assert isinstance(feed, Podcast) diff --git a/tests/test_extensibility.py b/tests/test_extensibility.py new file mode 100644 index 0000000..a340b19 --- /dev/null +++ b/tests/test_extensibility.py @@ -0,0 +1,75 @@ +from typing import Optional + +from pydantic import Field + +from rss_parser import RSSParser +from rss_parser.models import XMLBaseModel +from rss_parser.models.rss import RSS, Channel, Item +from rss_parser.models.types.tag import Tag + +PODCAST_XML = """ + + + Title + http://example.com + Description + Channel Author + + Episode 1 + 3600 + + +""" + + +class DurationItem(Item): + itunes_duration: Optional[Tag[str]] = Field(alias="itunes:duration", default=None) + + +class TestGenericSchemas: + def test_custom_item_is_one_parametrization_away(self): + rss = RSSParser.parse(PODCAST_XML, schema=RSS[Channel[DurationItem]]) + + item = rss.channel.content.items[0].content + assert isinstance(item, DurationItem) + assert item.itunes_duration.content == "3600" + + def test_custom_channel_subclass(self): + class MyChannel(Channel[DurationItem]): + itunes_author: Optional[Tag[str]] = Field(alias="itunes:author", default=None) + + rss = RSSParser.parse(PODCAST_XML, schema=RSS[MyChannel]) + + assert rss.channel.content.itunes_author.content == "Channel Author" + assert rss.channel.content.items[0].content.itunes_duration.content == "3600" + + def test_default_parametrization_is_unchanged(self): + rss = RSSParser.parse(PODCAST_XML) + + assert type(rss.channel.content.items[0].content) is Item + + +class TestUnknownTagsAreKept: + def test_unknown_tags_end_up_in_model_extra(self): + rss = RSSParser.parse(PODCAST_XML) + + assert rss.channel.content.model_extra["itunes:author"] == "Channel Author" + assert rss.channel.content.items[0].content.model_extra["itunes:duration"] == "3600" + + +class TestCustomRootSchema: + def test_schema_keyword_with_custom_root(self): + class CustomSchema(XMLBaseModel): + custom: Tag[str] + + rss = RSSParser.parse( + 'Custom tag data', + schema=CustomSchema, + ) + + assert rss.custom.content == "Custom tag data" + + def test_populate_by_name(self): + item = Item(title="hello") + + assert item.title.content == "hello" diff --git a/tests/test_itunes.py b/tests/test_itunes.py new file mode 100644 index 0000000..a559797 --- /dev/null +++ b/tests/test_itunes.py @@ -0,0 +1,77 @@ +from pathlib import Path + +from rss_parser import PodcastParser +from rss_parser.models.rss.itunes import ITunesChannel, ITunesItem + +APOLOGY_LINE = (Path(__file__).parent / "samples" / "podcast" / "apology_line" / "data.xml").read_text(encoding="utf-8") + +PODCAST_XML = """ + + + The Show + http://example.com + A show + Jane Host + serial + false + + + + + + + Jane Host + jane@example.com + + + Pilot + Pilot + 31:07 + 1 + 1 + full + + + +""" + + +class TestPodcastSchema: + def test_channel_level_tags(self): + podcast = PodcastParser.parse(PODCAST_XML) + channel = podcast.channel.content + + assert isinstance(channel, ITunesChannel) + assert channel.itunes_author.content == "Jane Host" + assert channel.itunes_type.content == "serial" + assert channel.itunes_explicit.content == "false" + assert channel.itunes_image.attributes == {"href": "http://example.com/art.jpg"} + assert channel.itunes_owner.content.name.content == "Jane Host" + assert channel.itunes_owner.content.email.content == "jane@example.com" + + def test_categories_including_nested(self): + channel = PodcastParser.parse(PODCAST_XML).channel.content + + assert channel.itunes_categories[0].attributes == {"text": "Comedy"} + # The nested category is kept raw in the content + assert channel.itunes_categories[1].attributes["text"] == "Society & Culture" + + def test_episode_level_tags(self): + item = PodcastParser.parse(PODCAST_XML).channel.content.items[0].content + + assert isinstance(item, ITunesItem) + assert item.itunes_duration.content == "31:07" + assert item.itunes_episode.content == 1 + assert item.itunes_season.content == 1 + assert item.itunes_episode_type.content == "full" + assert item.itunes_image.attributes == {"href": "http://example.com/ep1.jpg"} + + def test_real_world_feed(self): + podcast = PodcastParser.parse(APOLOGY_LINE) + channel = podcast.channel.content + + assert channel.itunes_author.content == "Wondery" + assert channel.itunes_owner.content.email.content == "iwonder@wondery.com" + assert channel.itunes_categories[0].attributes == {"text": "True Crime"} + assert channel.items[0].content.itunes_episode_type.content == "trailer" + assert all(item.content.itunes_duration is not None for item in channel.items) diff --git a/tests/test_parsing.py b/tests/test_parsing.py deleted file mode 100644 index f8b9363..0000000 --- a/tests/test_parsing.py +++ /dev/null @@ -1,94 +0,0 @@ -import sys -from typing import Type - -import pytest - -from rss_parser import AtomParser, BaseParser, RSSParser - -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 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: - @staticmethod - def compare_parsing(sample_and_result, parser: Type[BaseParser]): - sample, result = sample_and_result - rss = parser.parse(sample) - - assert rss - - if hasattr(rss, "model_dump"): - parsed = rss.model_dump() - else: - parsed = rss.dict() - assert parsed == result - - -RSS_PARSERS = pytest.mark.parametrize( - "parser_cls", - rss_parser_list, - ids=rss_ids, -) - -ATOM_PARSERS = pytest.mark.parametrize( - "parser_cls", - atom_parser_list, - ids=atom_ids, -) - - -@pytest.mark.usefixtures("sample_and_result") -class TestRSS: - @pytest.mark.parametrize( - "sample_and_result", - [ - ["rss_2"], - ["rss_2_no_category_attr"], - ["apology_line"], - ["rss_2_with_1_item"], - ["github-49"], - ], - indirect=True, - ) - @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") -class TestAtom: - @pytest.mark.parametrize( - "sample_and_result", - [ - ["atom"], - ["generic_atom_feed"], - ], - indirect=True, - ) - @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 diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py new file mode 100644 index 0000000..ca93698 --- /dev/null +++ b/tests/test_snapshots.py @@ -0,0 +1,23 @@ +import pytest + +from tests.conftest import PARSERS_BY_KIND, dump_for_snapshot, iter_samples, read_sample, read_snapshot + +SAMPLES = list(iter_samples()) + + +@pytest.mark.parametrize( + ("kind", "sample_dir"), + SAMPLES, + ids=[f"{kind}/{sample_dir.name}" for kind, sample_dir in SAMPLES], +) +def test_sample_matches_snapshot(kind, sample_dir): + """Every sample must parse and produce exactly the committed result.json. + + To update snapshots intentionally: python -m scripts.update_snapshots + """ + parser = PARSERS_BY_KIND[kind] + + parsed = parser.parse(read_sample(sample_dir)) + + assert parsed + assert dump_for_snapshot(parsed) == read_snapshot(sample_dir) diff --git a/tests/test_spec_compliance.py b/tests/test_spec_compliance.py new file mode 100644 index 0000000..33cbf32 --- /dev/null +++ b/tests/test_spec_compliance.py @@ -0,0 +1,94 @@ +from datetime import datetime + +import pytest +from pydantic import ValidationError + +from rss_parser import RSSParser + +CHANNEL_TEMPLATE = """ + + + Title + http://example.com + Description + {extra} + +""" + + +def parse_channel(extra: str = ""): + return RSSParser.parse(CHANNEL_TEMPLATE.format(extra=extra)).channel.content + + +class TestItemOptionality: + """All elements of an are optional, but at least one of title or description is required.""" + + def test_title_only_item(self): + channel = parse_channel("only title") + + assert channel.items[0].content.title.content == "only title" + assert channel.items[0].content.description is None + + def test_description_only_item(self): + channel = parse_channel("only description") + + assert channel.items[0].content.description.content == "only description" + assert channel.items[0].content.title is None + + def test_empty_item_is_rejected(self): + with pytest.raises(ValidationError, match="either or <description>"): + parse_channel("<item><guid>id-1</guid></item>") + + +class TestChannelElements: + def test_managing_editor(self): + channel = parse_channel("<managingEditor>geo@herald.com (George Matesky)</managingEditor>") + + assert channel.managing_editor.content == "geo@herald.com (George Matesky)" + + def test_skip_hours_and_days(self): + channel = parse_channel( + "<skipHours><hour>0</hour><hour>23</hour></skipHours>" + "<skipDays><day>Saturday</day><day>Sunday</day></skipDays>" + ) + + assert [hour.content for hour in channel.skip_hours.content.hours] == [0, 23] + assert [day.content for day in channel.skip_days.content.days] == ["Saturday", "Sunday"] + + def test_single_skip_hour_is_still_a_list(self): + channel = parse_channel("<skipHours><hour>12</hour></skipHours>") + + assert [hour.content for hour in channel.skip_hours.content.hours] == [12] + + def test_text_input_is_a_model(self): + channel = parse_channel( + "<textInput><title>SubmitSearch" + "qhttp://example.com/search
" + ) + + assert channel.text_input.content.name.content == "q" + assert channel.text_input.content.link.content == "http://example.com/search" + + def test_rating_is_a_string(self): + channel = parse_channel("(PICS-1.1 "http://www.rsac.org/ratingsv01.html")") + + assert channel.rating.content.startswith("(PICS-1.1") + + def test_ttl_is_an_int(self): + channel = parse_channel("60") + + assert channel.ttl.content == 60 + + def test_item_pub_date_is_parsed_to_datetime(self): + channel = parse_channel("tSat, 07 Sep 2002 00:00:01 GMT") + + assert isinstance(channel.items[0].content.pub_date.content, datetime) + + +class TestRSSVersions: + def test_rss_0_91_parses_with_version(self): + rss = RSSParser.parse( + 'TLD' + ) + + assert rss.version.content == "0.91" diff --git a/tests/test_tag.py b/tests/test_tag.py new file mode 100644 index 0000000..6dec37e --- /dev/null +++ b/tests/test_tag.py @@ -0,0 +1,73 @@ +import pytest + +from rss_parser.models import XMLBaseModel +from rss_parser.models.types.tag import Tag + + +class Model(XMLBaseModel): + width: Tag[int] + category: Tag[str] + + +def make_model(**overrides): + data = { + "width": 48, + "category": {"@someAttribute": "https://example.com", "#text": "valid string"}, + **overrides, + } + return Model.model_validate(data) + + +class TestContentAndAttributes: + def test_content_is_typed(self): + m = make_model() + + assert m.width.content == 48 + assert isinstance(m.width.content, int) + + def test_attributes_are_snake_cased_and_stripped(self): + m = make_model() + + assert m.category.attributes == {"some_attribute": "https://example.com"} + + def test_self_closing_tag_has_no_content(self): + m = make_model(category={"@href": "https://example.com"}) + + assert m.category.content is None + assert m.category.attributes == {"href": "https://example.com"} + + +class TestErgonomics: + def test_str_returns_content(self): + m = make_model() + + assert str(m.width) == "48" + assert str(m.category) == "valid string" + + def test_str_of_empty_tag_is_empty(self): + m = make_model(category={"@href": "x"}) + + assert str(m.category) == "" + + def test_bool(self): + m = make_model(category={"@href": "x"}) + + assert bool(m.width) + assert bool(m.category) # no content, but has attributes + assert not bool(Tag[str]()) + + def test_getattr_forwards_to_content(self): + m = make_model() + + assert m.category.upper() == "VALID STRING" + + def test_getattr_on_empty_content_mentions_self_closing_tag(self): + m = make_model(category={"@href": "x"}) + + with pytest.raises(AttributeError, match="self-closing"): + m.category.upper() + + def test_getitem_forwards_to_content(self): + m = make_model() + + assert m.category[:5] == "valid" From 095df6c566e0ff1c1cd5b5e9e2629cf62550d230 Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 00:30:35 +0200 Subject: [PATCH 10/20] ci: allow manual workflow runs via workflow_dispatch --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 104a62b..eeb7f10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,7 @@ name: Lint and test on: + workflow_dispatch: schedule: - cron: "0 0 1 * *" push: From 8ad98b4ae6f2d8374730ccabac7b9d98376e749a Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:01:40 +0200 Subject: [PATCH 11/20] build!: migrate from poetry to uv - pyproject converted to PEP 621 [project] metadata with hatchling as the build backend; dev/docs deps are PEP 735 dependency groups (dev installs by default, docs on demand) - poetry.lock/poetry.toml replaced by uv.lock - CI, docs and PyPI publish workflows use astral-sh/setup-uv with caching; publish builds with 'uv build' and uploads with 'uv publish' - pre-commit hooks and contributing docs updated to 'uv run' - xmltodict updated to 1.0.x per the existing >=0.13 constraint; test suite verified against it on Python 3.9 and 3.13 --- .github/workflows/ci.yml | 22 +- .github/workflows/docs.yml | 14 +- .github/workflows/publish_to_pypi.yml | 17 +- .pre-commit-config.yaml | 6 +- README.md | 4 +- docs/contributing.md | 21 +- poetry.lock | 1715 ------------------------- poetry.toml | 2 - pyproject.toml | 61 +- 9 files changed, 72 insertions(+), 1790 deletions(-) delete mode 100644 poetry.lock delete mode 100644 poetry.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeb7f10..5d08188 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,29 +26,23 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - - name: Install poetry - run: pipx install poetry - - - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} - id: setup-python - uses: actions/setup-python@v5.6.0 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: + enable-cache: true python-version: ${{ matrix.python-version }} - cache-dependency-path: pyproject.toml - cache: poetry - name: Install dependencies - if: steps.setup-python.outputs.cache-hit != 'true' - run: poetry install + run: uv sync --locked - name: Lint code with black - run: poetry run black --check . + run: uv run black --check . - name: Lint code with ruff - run: poetry run ruff check . + run: uv run ruff check . - name: Lint code with mypy - run: poetry run mypy rss_parser + run: uv run mypy rss_parser - name: Test code with pytest - run: poetry run pytest --doctest-modules rss_parser tests + run: uv run pytest --doctest-modules rss_parser tests diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1c70003..3cae32f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -19,18 +19,14 @@ jobs: steps: - uses: actions/checkout@v4.2.2 - - name: Install poetry - run: pipx install poetry - - - name: Set up Python - uses: actions/setup-python@v5.6.0 + - name: Install uv + uses: astral-sh/setup-uv@v6 with: + enable-cache: true python-version: "3.12" - cache-dependency-path: pyproject.toml - cache: poetry - name: Install dependencies - run: poetry install --with docs + run: uv sync --locked --group docs - name: Deploy to gh-pages - run: poetry run mkdocs gh-deploy --force + run: uv run mkdocs gh-deploy --force diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index ccbc5b3..89ed736 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -7,11 +7,16 @@ on: # TODO: Only on CI success jobs: - build-and-test-publish: + build-and-publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master - - name: Build and publish to pypi - uses: JRubics/poetry-publish@v1.9 - with: - pypi_token: ${{ secrets.pypi_password }} + - uses: actions/checkout@v4.2.2 + + - name: Install uv + uses: astral-sh/setup-uv@v6 + + - name: Build sdist and wheel + run: uv build + + - name: Publish to PyPI + run: uv publish --token ${{ secrets.pypi_password }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a7a3749..d8f3f23 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: hooks: - id: black-format-staged name: black - entry: poetry + entry: uv args: - run - black @@ -26,7 +26,7 @@ repos: - id: mypy-check-staged name: mypy - entry: poetry + entry: uv args: - run - mypy @@ -37,7 +37,7 @@ repos: - id: ruff-check-global name: ruff - entry: poetry + entry: uv args: - run - ruff diff --git a/README.md b/README.md index 36b312d..0622a0b 100644 --- a/README.md +++ b/README.md @@ -115,12 +115,12 @@ models generic and lossless. See the Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. -Install dependencies with `poetry install` (`pip install poetry`). +Install dependencies with `uv sync` ([install uv](https://docs.astral.sh/uv/getting-started/installation/)). Using `pre-commit` is highly recommended. To install hooks, run: ```bash -poetry run pre-commit install -t=pre-commit -t=pre-push +uv run pre-commit install -t=pre-commit -t=pre-push ``` See [Contributing](https://dhvcc.github.io/rss-parser/contributing/) for tests, snapshots, and docs. diff --git a/docs/contributing.md b/docs/contributing.md index fe6c7d6..8ad782b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -5,24 +5,25 @@ Pull requests are welcome. For major changes, please open an ## Setup +The project uses [uv](https://docs.astral.sh/uv/) for dependency management: + ```bash -pip install poetry -poetry install +uv sync ``` Using `pre-commit` is highly recommended. To install hooks, run: ```bash -poetry run pre-commit install -t=pre-commit -t=pre-push +uv run pre-commit install -t=pre-commit -t=pre-push ``` ## Running checks ```bash -poetry run pytest --doctest-modules rss_parser tests -poetry run black --check . -poetry run ruff check . -poetry run mypy rss_parser +uv run pytest --doctest-modules rss_parser tests +uv run black --check . +uv run ruff check . +uv run mypy rss_parser ``` ## Sample-based snapshot tests @@ -37,7 +38,7 @@ Dropping a new sample directory in is enough — the snapshot test discovers it To (re)generate `result.json` after adding a sample or intentionally changing model output: ```bash -poetry run python -m scripts.update_snapshots +uv run python -m scripts.update_snapshots ``` Review the resulting diff carefully — the snapshots are the contract. @@ -47,6 +48,6 @@ Review the resulting diff carefully — the snapshots are the contract. Docs are built with [mkdocs-material](https://squidfunk.github.io/mkdocs-material/): ```bash -poetry install --with docs -poetry run mkdocs serve +uv sync --group docs +uv run mkdocs serve ``` diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 35ac225..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1715 +0,0 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. - -[[package]] -name = "annotated-types" -version = "0.7.0" -description = "Reusable constraint types to use with typing.Annotated" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, - {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, -] - -[[package]] -name = "asttokens" -version = "3.0.1" -description = "Annotate AST trees with source code positions" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {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,<5)"] -test = ["astroid (>=2,<5)", "pytest (<9.0)", "pytest-cov", "pytest-xdist"] - -[[package]] -name = "babel" -version = "2.18.0" -description = "Internationalization utilities" -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35"}, - {file = "babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d"}, -] - -[package.extras] -dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] - -[[package]] -name = "backrefs" -version = "6.2" -description = "A wrapper around re and regex that adds additional back references." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8"}, - {file = "backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be"}, - {file = "backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90"}, - {file = "backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b"}, - {file = "backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7"}, - {file = "backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7"}, - {file = "backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49"}, -] - -[package.extras] -extras = ["regex"] - -[[package]] -name = "black" -version = "25.11.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {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] -click = ">=8.0.0" -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\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.10)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "certifi" -version = "2026.7.22" -description = "Python package for providing Mozilla's CA Bundle." -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775"}, - {file = "certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -description = "Validate configuration and produce human readable error messages." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.4.9" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee"}, - {file = "charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1"}, - {file = "charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0"}, - {file = "charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115"}, - {file = "charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b"}, - {file = "charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177"}, - {file = "charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-win32.whl", hash = "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b"}, - {file = "charset_normalizer-3.4.9-cp39-cp39-win_arm64.whl", hash = "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe"}, - {file = "charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5"}, - {file = "charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b"}, -] - -[[package]] -name = "click" -version = "8.1.8" -description = "Composable command line interface toolkit" -optional = false -python-versions = ">=3.7" -groups = ["dev", "docs"] -files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev", "docs"] -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] -markers = {dev = "sys_platform == \"win32\" or platform_system == \"Windows\""} - -[[package]] -name = "decorator" -version = "5.2.1" -description = "Decorators for Humans" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, - {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, -] - -[[package]] -name = "distlib" -version = "0.4.0" -description = "Distribution utilities" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, - {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, -] - -[[package]] -name = "exceptiongroup" -version = "1.3.0" -description = "Backport of PEP 654 (exception groups)" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, - {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.6.0", markers = "python_version < \"3.13\""} - -[package.extras] -test = ["pytest (>=6)"] - -[[package]] -name = "executing" -version = "2.2.1" -description = "Get the currently executing AST node of a frame, and other information" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {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] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] - -[[package]] -name = "filelock" -version = "3.19.1" -description = "A platform independent file lock." -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"}, - {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"}, -] - -[[package]] -name = "ghp-import" -version = "2.1.0" -description = "Copy your docs directly to the gh-pages branch." -optional = false -python-versions = "*" -groups = ["docs"] -files = [ - {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, - {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, -] - -[package.dependencies] -python-dateutil = ">=2.8.1" - -[package.extras] -dev = ["flake8", "markdown", "twine", "wheel"] - -[[package]] -name = "identify" -version = "2.6.15" -description = "File identification library for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {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] -license = ["ukkonen"] - -[[package]] -name = "idna" -version = "3.18" -description = "Internationalized Domain Names in Applications (IDNA)" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2"}, - {file = "idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848"}, -] - -[package.extras] -all = ["mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] - -[[package]] -name = "importlib-metadata" -version = "8.7.1" -description = "Read metadata from Python packages" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -markers = "python_version == \"3.9\"" -files = [ - {file = "importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151"}, - {file = "importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb"}, -] - -[package.dependencies] -zipp = ">=3.20" - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=3.4)"] -perf = ["ipython"] -test = ["flufl.flake8", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] -type = ["mypy (<1.19) ; platform_python_implementation == \"PyPy\"", "pytest-mypy (>=1.0.1)"] - -[[package]] -name = "iniconfig" -version = "2.1.0" -description = "brain-dead simple config-ini parsing" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, - {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, -] - -[[package]] -name = "ipython" -version = "8.18.1" -description = "IPython: Productive Interactive Computing" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397"}, - {file = "ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -decorator = "*" -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -jedi = ">=0.16" -matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -prompt-toolkit = ">=3.0.41,<3.1.0" -pygments = ">=2.4.0" -stack-data = "*" -traitlets = ">=5" -typing-extensions = {version = "*", markers = "python_version < \"3.10\""} - -[package.extras] -all = ["black", "curio", "docrepr", "exceptiongroup", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] -black = ["black"] -doc = ["docrepr", "exceptiongroup", "ipykernel", "matplotlib", "pickleshare", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio (<0.22)", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] -kernel = ["ipykernel"] -nbconvert = ["nbconvert"] -nbformat = ["nbformat"] -notebook = ["ipywidgets", "notebook"] -parallel = ["ipyparallel"] -qtconsole = ["qtconsole"] -test = ["pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath"] -test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.22)", "pandas", "pickleshare", "pytest (<7.1)", "pytest-asyncio (<0.22)", "testpath", "trio"] - -[[package]] -name = "jedi" -version = "0.19.2" -description = "An autocompletion tool for Python that can be used for text editors." -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, - {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, -] - -[package.dependencies] -parso = ">=0.8.4,<0.9.0" - -[package.extras] -docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] - -[[package]] -name = "jinja2" -version = "3.1.6" -description = "A very fast and expressive template engine." -optional = false -python-versions = ">=3.7" -groups = ["docs"] -files = [ - {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, - {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, -] - -[package.dependencies] -MarkupSafe = ">=2.0" - -[package.extras] -i18n = ["Babel (>=2.7)"] - -[[package]] -name = "markdown" -version = "3.9" -description = "Python implementation of John Gruber's Markdown." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280"}, - {file = "markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} - -[package.extras] -docs = ["mdx_gh_links (>=0.2)", "mkdocs (>=1.6)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] -testing = ["coverage", "pyyaml"] - -[[package]] -name = "markdown-it-py" -version = "3.0.0" -description = "Python port of markdown-it. Markdown parsing, done right!" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, - {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, -] - -[package.dependencies] -mdurl = ">=0.1,<1.0" - -[package.extras] -benchmarking = ["psutil", "pytest", "pytest-benchmark"] -code-style = ["pre-commit (>=3.0,<4.0)"] -compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] -linkify = ["linkify-it-py (>=1,<3)"] -plugins = ["mdit-py-plugins"] -profiling = ["gprof2dot"] -rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] -testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] - -[[package]] -name = "markupsafe" -version = "3.0.3" -description = "Safely add untrusted strings to HTML/XML markup." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559"}, - {file = "markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591"}, - {file = "markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6"}, - {file = "markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1"}, - {file = "markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8"}, - {file = "markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad"}, - {file = "markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf"}, - {file = "markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115"}, - {file = "markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a"}, - {file = "markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01"}, - {file = "markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e"}, - {file = "markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d"}, - {file = "markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f"}, - {file = "markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b"}, - {file = "markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c"}, - {file = "markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795"}, - {file = "markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676"}, - {file = "markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc"}, - {file = "markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12"}, - {file = "markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5"}, - {file = "markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73"}, - {file = "markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025"}, - {file = "markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb"}, - {file = "markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218"}, - {file = "markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe"}, - {file = "markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97"}, - {file = "markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf"}, - {file = "markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe"}, - {file = "markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581"}, - {file = "markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab"}, - {file = "markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50"}, - {file = "markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523"}, - {file = "markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9"}, - {file = "markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26"}, - {file = "markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42"}, - {file = "markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2"}, - {file = "markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d"}, - {file = "markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e"}, - {file = "markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8"}, - {file = "markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698"}, -] - -[[package]] -name = "matplotlib-inline" -version = "0.2.1" -description = "Inline Matplotlib backend for Jupyter" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {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" -description = "Markdown URL utilities" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, - {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, -] - -[[package]] -name = "mergedeep" -version = "1.3.4" -description = "A deep merge function for 🐍." -optional = false -python-versions = ">=3.6" -groups = ["docs"] -files = [ - {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, - {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, -] - -[[package]] -name = "mkdocs" -version = "1.6.1" -description = "Project documentation with Markdown." -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, - {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, -] - -[package.dependencies] -click = ">=7.0" -colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""} -ghp-import = ">=1.0" -importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} -jinja2 = ">=2.11.1" -markdown = ">=3.3.6" -markupsafe = ">=2.0.1" -mergedeep = ">=1.3.4" -mkdocs-get-deps = ">=0.2.0" -packaging = ">=20.5" -pathspec = ">=0.11.1" -pyyaml = ">=5.1" -pyyaml-env-tag = ">=0.1" -watchdog = ">=2.0" - -[package.extras] -i18n = ["babel (>=2.9.0)"] -min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.2" -description = "An extra command for MkDocs that infers required PyPI packages from `plugins` in mkdocs.yml" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650"}, - {file = "mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1"}, -] - -[package.dependencies] -importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""} -mergedeep = ">=1.3.4" -platformdirs = ">=2.2.0" -pyyaml = ">=5.1" - -[[package]] -name = "mkdocs-material" -version = "9.7.7" -description = "Documentation that simply works" -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs_material-9.7.7-py3-none-any.whl", hash = "sha256:8ea9bb1737a5b524a5f9dcf2e1b4ebda8274ae3008aa7845720a97083bef708f"}, - {file = "mkdocs_material-9.7.7.tar.gz", hash = "sha256:c0649c065b1b0512d60aad8c10f947f8e455284475239b364b610f2deb4d0855"}, -] - -[package.dependencies] -babel = ">=2.10" -backrefs = ">=5.7.post1" -colorama = ">=0.4" -jinja2 = ">=3.1" -markdown = ">=3.2" -mkdocs = ">=1.6,<2" -mkdocs-material-extensions = ">=1.3" -paginate = ">=0.5" -pygments = ">=2.16" -pymdown-extensions = ">=10.2" -requests = ">=2.30" - -[package.extras] -git = ["mkdocs-git-committers-plugin-2 (>=1.1)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4)"] -imaging = ["cairosvg (>=2.6)", "pillow (>=10.2)"] -recommended = ["mkdocs-minify-plugin (>=0.7)", "mkdocs-redirects (>=1.2)", "mkdocs-rss-plugin (>=1.6)"] - -[[package]] -name = "mkdocs-material-extensions" -version = "1.3.1" -description = "Extension pack for Python Markdown and MkDocs Material." -optional = false -python-versions = ">=3.8" -groups = ["docs"] -files = [ - {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, - {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, -] - -[[package]] -name = "mypy" -version = "1.18.2" -description = "Optional static typing for Python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {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] -mypy_extensions = ">=1.0.0" -pathspec = ">=0.9.0" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing_extensions = ">=4.6.0" - -[package.extras] -dmypy = ["psutil (>=4.0)"] -faster-cache = ["orjson"] -install-types = ["pip"] -mypyc = ["setuptools (>=50)"] -reports = ["lxml"] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -description = "Type system extensions for programs checked with the mypy type checker." -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, - {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, -] - -[[package]] -name = "nodeenv" -version = "1.9.1" -description = "Node.js virtual environment builder" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -files = [ - {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, - {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, -] - -[[package]] -name = "packaging" -version = "25.0" -description = "Core utilities for Python packages" -optional = false -python-versions = ">=3.8" -groups = ["dev", "docs"] -files = [ - {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, - {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, -] - -[[package]] -name = "paginate" -version = "0.5.7" -description = "Divides large result sets into pages for easier browsing" -optional = false -python-versions = "*" -groups = ["docs"] -files = [ - {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, - {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, -] - -[package.extras] -dev = ["pytest", "tox"] -lint = ["black"] - -[[package]] -name = "parso" -version = "0.8.5" -description = "A Python Parser" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {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] -qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] -testing = ["docopt", "pytest"] - -[[package]] -name = "pathspec" -version = "0.12.1" -description = "Utility library for gitignore style pattern matching of file paths." -optional = false -python-versions = ">=3.8" -groups = ["dev", "docs"] -files = [ - {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, - {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, -] - -[[package]] -name = "pexpect" -version = "4.9.0" -description = "Pexpect allows easy control of interactive console applications." -optional = false -python-versions = "*" -groups = ["dev"] -markers = "sys_platform != \"win32\"" -files = [ - {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, - {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, -] - -[package.dependencies] -ptyprocess = ">=0.5" - -[[package]] -name = "platformdirs" -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" -groups = ["dev", "docs"] -files = [ - {file = "platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85"}, - {file = "platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf"}, -] - -[package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.14.1)"] - -[[package]] -name = "pluggy" -version = "1.6.0" -description = "plugin and hook calling mechanisms for python" -optional = false -python-versions = ">=3.9" -groups = ["dev"] -files = [ - {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, - {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, -] - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["coverage", "pytest", "pytest-benchmark"] - -[[package]] -name = "pre-commit" -version = "2.21.0" -description = "A framework for managing and maintaining multi-language pre-commit hooks." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pre_commit-2.21.0-py2.py3-none-any.whl", hash = "sha256:e2f91727039fc39a92f58a588a25b87f936de6567eed4f0e673e0507edc75bad"}, - {file = "pre_commit-2.21.0.tar.gz", hash = "sha256:31ef31af7e474a8d8995027fefdfcf509b5c913ff31f2015b4ec4beb26a6f658"}, -] - -[package.dependencies] -cfgv = ">=2.0.0" -identify = ">=1.0.0" -nodeenv = ">=0.11.1" -pyyaml = ">=5.1" -virtualenv = ">=20.10.0" - -[[package]] -name = "prompt-toolkit" -version = "3.0.52" -description = "Library for building powerful interactive command lines in Python" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {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] -wcwidth = "*" - -[[package]] -name = "ptyprocess" -version = "0.7.0" -description = "Run a subprocess in a pseudo terminal" -optional = false -python-versions = "*" -groups = ["dev"] -markers = "sys_platform != \"win32\"" -files = [ - {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, - {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, -] - -[[package]] -name = "pure-eval" -version = "0.2.3" -description = "Safely evaluate AST nodes without side effects" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, - {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, -] - -[package.extras] -tests = ["pytest"] - -[[package]] -name = "pydantic" -version = "2.12.4" -description = "Data validation using Python type hints" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {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.41.5" -typing-extensions = ">=4.14.1" -typing-inspection = ">=0.4.2" - -[package.extras] -email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] - -[[package]] -name = "pydantic-core" -version = "2.41.5" -description = "Core functionality for Pydantic validation and serialization" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {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.14.1" - -[[package]] -name = "pygments" -version = "2.19.2" -description = "Pygments is a syntax highlighting package written in Python." -optional = false -python-versions = ">=3.8" -groups = ["dev", "docs"] -files = [ - {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, - {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, -] - -[package.extras] -windows-terminal = ["colorama (>=0.4.6)"] - -[[package]] -name = "pymdown-extensions" -version = "10.21.3" -description = "Extension pack for Python Markdown." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6"}, - {file = "pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354"}, -] - -[package.dependencies] -markdown = ">=3.6" -pyyaml = "*" - -[package.extras] -extra = ["pygments (>=2.19.1)"] - -[[package]] -name = "pytest" -version = "7.4.4" -description = "pytest: simple powerful testing with Python" -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, - {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, -] - -[package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -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 = "python-dateutil" -version = "2.9.0.post0" -description = "Extensions to the standard Python datetime module" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["docs"] -files = [ - {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, - {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, -] - -[package.dependencies] -six = ">=1.5" - -[[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" -groups = ["dev"] -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.3" -description = "YAML parser and emitter for Python" -optional = false -python-versions = ">=3.8" -groups = ["dev", "docs"] -files = [ - {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 = "pyyaml-env-tag" -version = "1.1" -description = "A custom YAML tag for referencing environment variables in YAML files." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, - {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, -] - -[package.dependencies] -pyyaml = "*" - -[[package]] -name = "requests" -version = "2.32.5" -description = "Python HTTP for Humans." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6"}, - {file = "requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"}, -] - -[package.dependencies] -certifi = ">=2017.4.17" -charset_normalizer = ">=2,<4" -idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<3" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)"] -use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] - -[[package]] -name = "rich" -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" -groups = ["dev"] -files = [ - {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" - -[package.extras] -jupyter = ["ipywidgets (>=7.5.1,<9)"] - -[[package]] -name = "ruff" -version = "0.14.5" -description = "An extremely fast Python linter and code formatter, written in Rust." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {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]] -name = "six" -version = "1.17.0" -description = "Python 2 and 3 compatibility utilities" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["docs"] -files = [ - {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, - {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, -] - -[[package]] -name = "stack-data" -version = "0.6.3" -description = "Extract data from python stack frames and tracebacks for informative displays" -optional = false -python-versions = "*" -groups = ["dev"] -files = [ - {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, - {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, -] - -[package.dependencies] -asttokens = ">=2.1.0" -executing = ">=1.2.0" -pure-eval = "*" - -[package.extras] -tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] - -[[package]] -name = "tomli" -version = "2.3.0" -description = "A lil' TOML parser" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -markers = "python_version < \"3.11\"" -files = [ - {file = "tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45"}, - {file = "tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf"}, - {file = "tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845"}, - {file = "tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c"}, - {file = "tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456"}, - {file = "tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac"}, - {file = "tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f"}, - {file = "tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8"}, - {file = "tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6"}, - {file = "tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876"}, - {file = "tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b"}, - {file = "tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b"}, - {file = "tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f"}, - {file = "tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05"}, - {file = "tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606"}, - {file = "tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e"}, - {file = "tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc"}, - {file = "tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879"}, - {file = "tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005"}, - {file = "tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463"}, - {file = "tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77"}, - {file = "tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530"}, - {file = "tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67"}, - {file = "tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f"}, - {file = "tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0"}, - {file = "tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba"}, - {file = "tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b"}, - {file = "tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549"}, -] - -[[package]] -name = "traitlets" -version = "5.14.3" -description = "Traitlets Python configuration system" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, - {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, -] - -[package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] -test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] - -[[package]] -name = "types-xmltodict" -version = "0.14.0.20241009" -description = "Typing stubs for xmltodict" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {file = "types-xmltodict-0.14.0.20241009.tar.gz", hash = "sha256:9224c2422c5b6359cf826685b4ee50b14dc2cb9134561ab793ef6b03dd7108e1"}, - {file = "types_xmltodict-0.14.0.20241009-py3-none-any.whl", hash = "sha256:92812e17ffa9171416b35806cb5f4ed3f8f52b6724b2c555e4733e902ef4afd0"}, -] - -[[package]] -name = "typing-extensions" -version = "4.15.0" -description = "Backported and Experimental Type Hints for Python 3.9+" -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, -] - -[[package]] -name = "typing-inspection" -version = "0.4.2" -description = "Runtime typing introspection tools" -optional = false -python-versions = ">=3.9" -groups = ["main"] -files = [ - {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] -typing-extensions = ">=4.12.0" - -[[package]] -name = "urllib3" -version = "2.6.3" -description = "HTTP library with thread-safe connection pooling, file post, and more." -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4"}, - {file = "urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed"}, -] - -[package.extras] -brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] -h2 = ["h2 (>=4,<5)"] -socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] - -[[package]] -name = "virtualenv" -version = "20.35.4" -description = "Virtual Python Environment builder" -optional = false -python-versions = ">=3.8" -groups = ["dev"] -files = [ - {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)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] - -[[package]] -name = "watchdog" -version = "6.0.0" -description = "Filesystem events monitoring" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -files = [ - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, - {file = "watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2"}, - {file = "watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860"}, - {file = "watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134"}, - {file = "watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a"}, - {file = "watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881"}, - {file = "watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa"}, - {file = "watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c"}, - {file = "watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2"}, - {file = "watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a"}, - {file = "watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680"}, - {file = "watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f"}, - {file = "watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"}, -] - -[package.extras] -watchmedo = ["PyYAML (>=3.10)"] - -[[package]] -name = "wcwidth" -version = "0.2.14" -description = "Measures the displayed width of unicode strings in a terminal" -optional = false -python-versions = ">=3.6" -groups = ["dev"] -files = [ - {file = "wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1"}, - {file = "wcwidth-0.2.14.tar.gz", hash = "sha256:4d478375d31bc5395a3c55c40ccdf3354688364cd61c4f6adacaa9215d0b3605"}, -] - -[[package]] -name = "xmltodict" -version = "0.13.0" -description = "Makes working with XML feel like you are working with JSON" -optional = false -python-versions = ">=3.4" -groups = ["main"] -files = [ - {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, - {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, -] - -[[package]] -name = "zipp" -version = "3.23.1" -description = "Backport of pathlib-compatible object wrapper for zip files" -optional = false -python-versions = ">=3.9" -groups = ["docs"] -markers = "python_version == \"3.9\"" -files = [ - {file = "zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc"}, - {file = "zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110"}, -] - -[package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] -cover = ["pytest-cov"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] -type = ["pytest-mypy"] - -[metadata] -lock-version = "2.1" -python-versions = "^3.9" -content-hash = "79cc47fc9b52e42b67dffd8b1478715fc6f0598604490db3c2db33274a66182b" diff --git a/poetry.toml b/poetry.toml deleted file mode 100644 index ab1033b..0000000 --- a/poetry.toml +++ /dev/null @@ -1,2 +0,0 @@ -[virtualenvs] -in-project = true diff --git a/pyproject.toml b/pyproject.toml index ae285da..8773de3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,10 +1,11 @@ -[tool.poetry] +[project] name = "rss-parser" version = "4.0.0" description = "Typed pythonic RSS/Atom parser" -authors = ["dhvcc <1337kwiz@gmail.com>"] -license = "GPL-3.0" +authors = [{ name = "dhvcc", email = "1337kwiz@gmail.com" }] +license = { text = "GPL-3.0" } readme = "README.md" +requires-python = ">=3.9" keywords = [ "python", "python3", @@ -31,35 +32,37 @@ classifiers = [ "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", ] -packages = [{ include = "rss_parser" }, { include = "rss_parser/py.typed" }] - +dependencies = [ + "pydantic>=2.7,<3.0", + "xmltodict>=0.13.0", + "typing-extensions>=4.6", +] -[tool.poetry.urls] -"Homepage" = "https://dhvcc.github.io/rss-parser" -"Source" = "https://github.com/dhvcc/rss-parser" +[project.urls] +Homepage = "https://dhvcc.github.io/rss-parser" +Source = "https://github.com/dhvcc/rss-parser" "Bug Tracker" = "https://github.com/dhvcc/rss-parser/issues" -[tool.poetry.dependencies] -python = "^3.9" -pydantic = ">=2.7,<3.0" -xmltodict = ">=0.13.0" -typing-extensions = ">=4.6" - -[tool.poetry.group.dev.dependencies] -ipython = "*" -black = "*" -pre-commit = "^2.12.0" -ruff = "*" -rich = "*" -pytest = "^7.4.0" -mypy = "*" -types-xmltodict = "^0.14.0.20241009" +[dependency-groups] +dev = [ + "ipython", + "black", + "pre-commit>=2.12.0", + "ruff", + "rich", + "pytest>=7.4.0", + "mypy", + "types-xmltodict>=0.14.0.20241009", +] +docs = [ + "mkdocs-material>=9.5", +] -[tool.poetry.group.docs] -optional = true +[tool.uv] +default-groups = ["dev"] -[tool.poetry.group.docs.dependencies] -mkdocs-material = "^9.5" +[tool.hatch.build.targets.wheel] +packages = ["rss_parser"] [tool.pytest.ini_options] addopts = "--color=yes" @@ -109,5 +112,5 @@ select = [ "rss_parser/models/atom/**" = ["A003"] [build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" +requires = ["hatchling"] +build-backend = "hatchling.build" From a715356a0399d008823af95b1634863d9fe80ded Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:02:20 +0200 Subject: [PATCH 12/20] build: commit uv.lock, un-ignore it over the global gitignore Also migrate pre-commit config to non-deprecated stage names. --- .gitignore | 3 + .pre-commit-config.yaml | 10 +- uv.lock | 2107 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 2115 insertions(+), 5 deletions(-) create mode 100644 uv.lock diff --git a/.gitignore b/.gitignore index 4174727..cc96abf 100644 --- a/.gitignore +++ b/.gitignore @@ -119,3 +119,6 @@ venv.bak/ # mkdocs build output site/ + +# uv lockfile must be committed (overrides global ignore) +!uv.lock diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d8f3f23..096bf03 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,10 +5,10 @@ repos: hooks: # Check for files that contain merge conflict strings. - id: check-merge-conflict - stages: [ commit, push ] + stages: [ pre-commit, pre-push ] # Simply check whether files parse as valid python. - id: check-ast - stages: [ commit ] + stages: [ pre-commit ] # Use locally installed hooks - repo: local @@ -22,7 +22,7 @@ repos: - black language: system types: [ python ] - stages: [ commit ] + stages: [ pre-commit ] - id: mypy-check-staged name: mypy @@ -33,7 +33,7 @@ repos: - rss_parser pass_filenames: false language: system - stages: [ push ] + stages: [ pre-push ] - id: ruff-check-global name: ruff @@ -44,4 +44,4 @@ repos: - check language: system types: [ python ] - stages: [ commit, push ] \ No newline at end of file + stages: [ pre-commit, pre-push ] \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..7a8aabd --- /dev/null +++ b/uv.lock @@ -0,0 +1,2107 @@ +version = 1 +revision = 3 +requires-python = ">=3.9" +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", + "python_full_version < '3.10'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "ast-serialize" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/ad/0d70a3a2d6e01968d985415259e8ec7ad3f777903f9b1c1f3c8c44642c60/ast_serialize-0.6.0.tar.gz", hash = "sha256:aadd3ffcf4858c9726bf3515f7b199c7eadbe504f96028e4a87172c0da65a8fe", size = 61489, upload-time = "2026-06-30T20:02:55.555Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/12/3e5f575f156555547c250a8b0d1347517a3a20fc7f4492e9703a69d4f45e/ast_serialize-0.6.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:a7520b672827885bafeae7501f684d14d47d17e5f45256f9df547686cca52264", size = 1177640, upload-time = "2026-06-30T20:02:06.708Z" }, + { url = "https://files.pythonhosted.org/packages/a2/a4/921a9e27951627983b0f368859ea00f8330a551dc0bf4c2fdcb11855a98b/ast_serialize-0.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a14191beec7e0c078d2fc1f6edc0aee88bcd4db9f18e1bc9f8052b559c22dddc", size = 1168111, upload-time = "2026-06-30T20:02:08.366Z" }, + { url = "https://files.pythonhosted.org/packages/00/69/950cf404de7b8782cf95e5c1237e25e2aa46177b287f39f9eeddf481fd6f/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32ef62ec34cf6be20ad77d4799556638fbdf187f3ae10698dfb20ef9f2c89516", size = 1227656, upload-time = "2026-06-30T20:02:09.843Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a8/46f8f6a6479d9d2273980957bb091a506c55f5b95d3c029ee58518a78407/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:13b7769970a39983b0adf2f38917b1cd3b8946f76df045756c3d741bc689f089", size = 1227706, upload-time = "2026-06-30T20:02:11.367Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/9ac415bda0a40e49eab8fea3b2741c19c98bb84d57d62c4cfc6230eb67be/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f7a408601bb3edaefb3bc67a4c01f5235e3253653b6a5729a2ee2382b35341c", size = 1431705, upload-time = "2026-06-30T20:02:12.737Z" }, + { url = "https://files.pythonhosted.org/packages/e5/06/8807115d441444879f7561b5eede5ac18fc80392f11826d61ccf31f503b1/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8670bfa51208a2c0c8d138928e40e998fab158f9200d53bb80c088b5b8eda7b8", size = 1249533, upload-time = "2026-06-30T20:02:14.571Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/c2ba82ef9618650357d9421a1fdb27ffec862a7f57e8e2de82a3ccd11e12/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4826809eb8597a8cd59fd924b6d7c285b8969a1e0007e2cb652cab62376270f", size = 1252619, upload-time = "2026-06-30T20:02:16.219Z" }, + { url = "https://files.pythonhosted.org/packages/0f/a7/fa31d52dd4102cede29fb9634e98d214129b2783b4f95528c6dc6a8f6587/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:577a6c189068686869f5f1ddc38363f3ae1808a4753b577266f9202071a7bb66", size = 1242983, upload-time = "2026-06-30T20:02:17.813Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/ddf742b5ad3c4bafd3466f2265037cfd99bc1b9a5ee46a5d58c90d523242/ast_serialize-0.6.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:085de7f62dc9cc247eb01e965a362707d1d90b1d89a82c5bf78301a60a3c417b", size = 1296148, upload-time = "2026-06-30T20:02:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/24/cb/9f6f217cce8b3b632c5568b478d195a35e79dce4dbe309438cb89ba6ea4f/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9f8a8b78b13173de6a9ec22111d9be674874cd5bdccda04f14ae5ebc2bef403a", size = 1403826, upload-time = "2026-06-30T20:02:20.696Z" }, + { url = "https://files.pythonhosted.org/packages/2d/f8/9d16d4f0107a183924425cc0e7618d8bf76f96b45afa9ff19f924ed1ad57/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:f2ff3baffc3a29c1f15bc9098aa0c09763410262d5e6cef42116f7356c184554", size = 1502943, upload-time = "2026-06-30T20:02:22.034Z" }, + { url = "https://files.pythonhosted.org/packages/80/dd/bbc1c38756350dddf7e24acae1c9482ef42051c267417e019aecc1ed4075/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0067b25fce104eaae5b88383de9ab803faeb671831e14ca698b771b356e2600f", size = 1497632, upload-time = "2026-06-30T20:02:23.517Z" }, + { url = "https://files.pythonhosted.org/packages/42/7e/9daffefcf5b97e6bb4c3e0b3c024c1aee9722f23d3cf7cd2ff80d6fb4a40/ast_serialize-0.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c617417f9cbb0cb144f6283c3cbe0d2e0f01beaf9f608f662b21191058a626ec", size = 1448858, upload-time = "2026-06-30T20:02:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/e5/1f/f9baaab81a677ea0af7d2458cac2f94ebcc85958f8a3c15ba9d9e5dab653/ast_serialize-0.6.0-cp314-cp314t-win32.whl", hash = "sha256:5337cb256dcea3df9288205213d1601581536526b8f4da44b6974f1180f3252a", size = 1052600, upload-time = "2026-06-30T20:02:26.263Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1f/41b535866519512d8cf6669cb2cff7823b7672bb6279c0333b4ff89d7d9f/ast_serialize-0.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2d947e45cafc4b09bd7528917fa84c517654a43de173c79785574b7b3068ac24", size = 1095570, upload-time = "2026-06-30T20:02:27.639Z" }, + { url = "https://files.pythonhosted.org/packages/50/64/e472fe3e3a2d33d874b987e8518aedf24562919e3b6161a4fa1797e89c0f/ast_serialize-0.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:6e15ec740436e1a0d62de848641abe5f3a2f89a7f94907d534795ac91bbacf14", size = 1067267, upload-time = "2026-06-30T20:02:28.949Z" }, + { url = "https://files.pythonhosted.org/packages/52/19/ac8348ae8711c9b5ae834634f635780cab62a0f5e6f988882e048b89c2ae/ast_serialize-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:093cb8bb91b720d8523580498d031791bb1bbaa048599c3d21085d380e11a596", size = 1185367, upload-time = "2026-06-30T20:02:30.427Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f6/ec7ec652c51db77c2f61d8573338e13e4704303265ccc658cb4031d9f354/ast_serialize-0.6.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:e61580a69faf47e3689795367ed211f2a10fd741478cc0f36a0f128793360aad", size = 1178657, upload-time = "2026-06-30T20:02:31.964Z" }, + { url = "https://files.pythonhosted.org/packages/6f/02/613a7534a41d0122f37d1e0c64aa8ac78bfb831f8c92f6db057a311abb3c/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305802f2ce2a7c4e87835078ea85c58b586ddda8095b92fe2ead9364ae19c80a", size = 1238620, upload-time = "2026-06-30T20:02:33.664Z" }, + { url = "https://files.pythonhosted.org/packages/4d/21/087957bba486242afc52f49b2d9e21c9dad00289356cf9efe67084015a9d/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c7b8b8f0c42f752ea00b2b7d7c090b3f80d9c1c5c75cadf16423790a0cc74081", size = 1236075, upload-time = "2026-06-30T20:02:34.936Z" }, + { url = "https://files.pythonhosted.org/packages/82/04/78128bbb170071c2c72a210a181f1c00e11cc1cec60a8beef747b07f9201/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd5b91b9e6f2356ace3a556963b0cd783b395fbbb0bb17b4defc283415466e77", size = 1441348, upload-time = "2026-06-30T20:02:36.245Z" }, + { url = "https://files.pythonhosted.org/packages/64/64/62fb99d6faf199b4c3e5b08a07136e9a0d7664bb249c6de3670e5b63e9b6/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d6ef91590258ada18909b9caea344dac4de2013906b035473cd674a43f4b790", size = 1258580, upload-time = "2026-06-30T20:02:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/ca/87/b4d6c38e0ccd5e85dc54cecdf933a152c60b28fe5d993a6d8a72fa6d5896/ast_serialize-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbed41e9386059fc0261d602445ede0976c2ecec2939688bcbcb9ed0b6f28b7", size = 1261693, upload-time = "2026-06-30T20:02:39.123Z" }, + { url = "https://files.pythonhosted.org/packages/0e/4b/3676ca2191f39bafb75f93f99b2f429ec464586158fece2165f3572805dc/ast_serialize-0.6.0-cp39-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:cdc4e6f930b9090c2f92c9036ad12ffb8e6e44d4a5ba06f1458a05d60f203f7b", size = 1252517, upload-time = "2026-06-30T20:02:40.511Z" }, + { url = "https://files.pythonhosted.org/packages/f3/58/494ef8c4b4acb2f4a265ac934caf45f792a08fe27d6b853de35ad991941a/ast_serialize-0.6.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:897ac47b5637be41c0c07061c8a912fafa967ef1dc73fa115e4bfa70882a093b", size = 1304843, upload-time = "2026-06-30T20:02:41.961Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f2/13736d920ab3d49bbee80ef1a277dd7b7aaf3b3545efd9d2a8114fe05525/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c4af9a1386166e40ed01464991806f89038a2d89782576c7774876fa77034e32", size = 1413698, upload-time = "2026-06-30T20:02:44.179Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5a/e046f3899e2acba4677d7427b76431443a1aa1a0e583dfb05b55b69d55cf/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c901adbd750029b9ac4ad3d6aa56853e0ad4875119fbf52b7b8298afc223828b", size = 1512209, upload-time = "2026-06-30T20:02:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c7/e42aaca7bb2d22a7c06d5a8c7930086c5a334e93d716e6fa5e6647a4515f/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae22a366b752ab4496191525b78b097b5b72d531752e3c1dd7e383a8f2c8a1a", size = 1508464, upload-time = "2026-06-30T20:02:46.942Z" }, + { url = "https://files.pythonhosted.org/packages/95/93/5524a3dc6c3f593de3228ed9cbef73afa047625b7000ec21b7f58e6eb4d4/ast_serialize-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4ed29121da8b3fdc291002801a1de0f76248fa07dce89157a5f277842cf6126e", size = 1457164, upload-time = "2026-06-30T20:02:48.294Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c0/36a6ffb4d653cf621427b4c4928671f53ad800c453474de2b82564a44ad9/ast_serialize-0.6.0-cp39-abi3-pyemscripten_2026_0_wasm32.whl", hash = "sha256:b1dac4e09d341c1300ba69cdcbe62867b32a8c75d90db9bf4d083bec3b039f0b", size = 863014, upload-time = "2026-06-30T20:02:49.742Z" }, + { url = "https://files.pythonhosted.org/packages/09/c7/7d5ad8b49e1278e1c2a1e0274bd7850560b3f09313aa00c13bc8d5544792/ast_serialize-0.6.0-cp39-abi3-win32.whl", hash = "sha256:82c312a7844d2fdeb4d5c48bd3d215bf940dafd4704e1a9bcf252a99010a99b1", size = 1063165, upload-time = "2026-06-30T20:02:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/47/ae/6710c14ecb276031cf10249f6adf5a59e2d3fdb3b5183bd59f70524067ee/ast_serialize-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:113b58346f9ceb664352032770caca817d4a3c86f611c6088e6ef65ddaa70f0e", size = 1101444, upload-time = "2026-06-30T20:02:52.554Z" }, + { url = "https://files.pythonhosted.org/packages/66/40/c53deb2cd0c9b0fb636d24d9f40924cf2e65028e6b20b10cd5c1eeb2c730/ast_serialize-0.6.0-cp39-abi3-win_arm64.whl", hash = "sha256:ccd132fe8db56f61fe743b1f644d01b8d65b83248a8da506f3132bda86d6ed5e", size = 1072965, upload-time = "2026-06-30T20:02:54.097Z" }, +] + +[[package]] +name = "asttokens" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/25/1e/faf0f247f6f881b98fc4d6d07e14085cb89d13665084e6d6ac1dc2c03d0b/asttokens-3.0.2.tar.gz", hash = "sha256:3ecdbd8f2cc195f53ccada3a613538bb5f9ef6f6869129f13e03c30a677b8fe2", size = 63136, upload-time = "2026-07-12T03:31:49.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, +] + +[[package]] +name = "babel" +version = "2.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, +] + +[[package]] +name = "backrefs" +version = "6.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, +] + +[[package]] +name = "backrefs" +version = "7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/a7dd63622beef68cc0d3c3c36d472e143dd95443d5ebf14cd1a5b4dfbf11/backrefs-7.0.tar.gz", hash = "sha256:4989bb9e1e99eb23647c7160ed51fb21d0b41b5d200f2d3017da41e023097e82", size = 7012453, upload-time = "2026-04-28T16:28:04.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/39/39a31d7eae729ea14ed10c3ccef79371197177b9355a86cb3525709e8502/backrefs-7.0-py310-none-any.whl", hash = "sha256:b57cd227ea556b0aed3dc9b8da4628db4eabc0402c6d7fcfc69283a93955f7e9", size = 380824, upload-time = "2026-04-28T16:27:55.647Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b5/9302644225ba7dfa934a2ff2b9c7bb85701313a90dddb3dfaf693fa5bae2/backrefs-7.0-py311-none-any.whl", hash = "sha256:a0fa7360c63509e9e077e174ef4e6d3c21c8db94189b9d957289ae6d794b9475", size = 392626, upload-time = "2026-04-28T16:27:57.42Z" }, + { url = "https://files.pythonhosted.org/packages/36/da/87912ddec6e06feffbaa3d7aa18fc6352bee2e8f1fee185d7d1690f8f4e8/backrefs-7.0-py312-none-any.whl", hash = "sha256:ca42ce6a49ace3d75684dfa9937f3373902a63284ecb385ce36d15e5dcb41c12", size = 398537, upload-time = "2026-04-28T16:27:58.913Z" }, + { url = "https://files.pythonhosted.org/packages/00/bb/90ba423612b6aa0adccc6b1874bcd4a9b44b660c0c16f346611e00f64ac3/backrefs-7.0-py313-none-any.whl", hash = "sha256:f2c52955d631b9e1ac4cd56209f0a3a946d592b98e7790e77699339ae01c102a", size = 400491, upload-time = "2026-04-28T16:28:00.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5c/fb93d3092640a24dfb7bd7727a24016d7c01774ca013e60efd3f683c8002/backrefs-7.0-py314-none-any.whl", hash = "sha256:a6448b28180e3ca01134c9cf09dcebafad8531072e09903c5451748a05f24bc9", size = 412349, upload-time = "2026-04-28T16:28:02.412Z" }, +] + +[[package]] +name = "black" +version = "25.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" } }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pytokens" }, + { name = "tomli" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/ad/33adf4708633d047950ff2dfdea2e215d84ac50ef95aff14a614e4b6e9b2/black-25.11.0.tar.gz", hash = "sha256:9a323ac32f5dc75ce7470501b887250be5005a01602e931a15e45593f70f6e08", size = 655669, upload-time = "2025-11-10T01:53:50.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/d2/6caccbc96f9311e8ec3378c296d4f4809429c43a6cd2394e3c390e86816d/black-25.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ec311e22458eec32a807f029b2646f661e6859c3f61bc6d9ffb67958779f392e", size = 1743501, upload-time = "2025-11-10T01:59:06.202Z" }, + { url = "https://files.pythonhosted.org/packages/69/35/b986d57828b3f3dccbf922e2864223197ba32e74c5004264b1c62bc9f04d/black-25.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1032639c90208c15711334d681de2e24821af0575573db2810b0763bcd62e0f0", size = 1597308, upload-time = "2025-11-10T01:57:58.633Z" }, + { url = "https://files.pythonhosted.org/packages/39/8e/8b58ef4b37073f52b64a7b2dd8c9a96c84f45d6f47d878d0aa557e9a2d35/black-25.11.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c0f7c461df55cf32929b002335883946a4893d759f2df343389c4396f3b6b37", size = 1656194, upload-time = "2025-11-10T01:57:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/8d/30/9c2267a7955ecc545306534ab88923769a979ac20a27cf618d370091e5dd/black-25.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:f9786c24d8e9bd5f20dc7a7f0cdd742644656987f6ea6947629306f937726c03", size = 1347996, upload-time = "2025-11-10T01:57:22.391Z" }, + { url = "https://files.pythonhosted.org/packages/c4/62/d304786b75ab0c530b833a89ce7d997924579fb7484ecd9266394903e394/black-25.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:895571922a35434a9d8ca67ef926da6bc9ad464522a5fe0db99b394ef1c0675a", size = 1727891, upload-time = "2025-11-10T02:01:40.507Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/ffe8a006aa522c9e3f430e7b93568a7b2163f4b3f16e8feb6d8c3552761a/black-25.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb4f4b65d717062191bdec8e4a442539a8ea065e6af1c4f4d36f0cdb5f71e170", size = 1581875, upload-time = "2025-11-10T01:57:51.192Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7c8bda3108d0bb57387ac41b4abb5c08782b26da9f9c4421ef6694dac01a/black-25.11.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d81a44cbc7e4f73a9d6ae449ec2317ad81512d1e7dce7d57f6333fd6259737bc", size = 1642716, upload-time = "2025-11-10T01:56:51.589Z" }, + { url = "https://files.pythonhosted.org/packages/34/b9/f17dea34eecb7cc2609a89627d480fb6caea7b86190708eaa7eb15ed25e7/black-25.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:7eebd4744dfe92ef1ee349dc532defbf012a88b087bb7ddd688ff59a447b080e", size = 1352904, upload-time = "2025-11-10T01:59:26.252Z" }, + { url = "https://files.pythonhosted.org/packages/7f/12/5c35e600b515f35ffd737da7febdb2ab66bb8c24d88560d5e3ef3d28c3fd/black-25.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:80e7486ad3535636657aa180ad32a7d67d7c273a80e12f1b4bfa0823d54e8fac", size = 1772831, upload-time = "2025-11-10T02:03:47Z" }, + { url = "https://files.pythonhosted.org/packages/1a/75/b3896bec5a2bb9ed2f989a970ea40e7062f8936f95425879bbe162746fe5/black-25.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cced12b747c4c76bc09b4db057c319d8545307266f41aaee665540bc0e04e96", size = 1608520, upload-time = "2025-11-10T01:58:46.895Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b5/2bfc18330eddbcfb5aab8d2d720663cd410f51b2ed01375f5be3751595b0/black-25.11.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb2d54a39e0ef021d6c5eef442e10fd71fcb491be6413d083a320ee768329dd", size = 1682719, upload-time = "2025-11-10T01:56:55.24Z" }, + { url = "https://files.pythonhosted.org/packages/96/fb/f7dc2793a22cdf74a72114b5ed77fe3349a2e09ef34565857a2f917abdf2/black-25.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:ae263af2f496940438e5be1a0c1020e13b09154f3af4df0835ea7f9fe7bfa409", size = 1362684, upload-time = "2025-11-10T01:57:07.639Z" }, + { url = "https://files.pythonhosted.org/packages/ad/47/3378d6a2ddefe18553d1115e36aea98f4a90de53b6a3017ed861ba1bd3bc/black-25.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0a1d40348b6621cc20d3d7530a5b8d67e9714906dfd7346338249ad9c6cedf2b", size = 1772446, upload-time = "2025-11-10T02:02:16.181Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4b/0f00bfb3d1f7e05e25bfc7c363f54dc523bb6ba502f98f4ad3acf01ab2e4/black-25.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:51c65d7d60bb25429ea2bf0731c32b2a2442eb4bd3b2afcb47830f0b13e58bfd", size = 1607983, upload-time = "2025-11-10T02:02:52.502Z" }, + { url = "https://files.pythonhosted.org/packages/99/fe/49b0768f8c9ae57eb74cc10a1f87b4c70453551d8ad498959721cc345cb7/black-25.11.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:936c4dd07669269f40b497440159a221ee435e3fddcf668e0c05244a9be71993", size = 1682481, upload-time = "2025-11-10T01:57:12.35Z" }, + { url = "https://files.pythonhosted.org/packages/55/17/7e10ff1267bfa950cc16f0a411d457cdff79678fbb77a6c73b73a5317904/black-25.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:f42c0ea7f59994490f4dccd64e6b2dd49ac57c7c84f38b8faab50f8759db245c", size = 1363869, upload-time = "2025-11-10T01:58:24.608Z" }, + { url = "https://files.pythonhosted.org/packages/67/c0/cc865ce594d09e4cd4dfca5e11994ebb51604328489f3ca3ae7bb38a7db5/black-25.11.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:35690a383f22dd3e468c85dc4b915217f87667ad9cce781d7b42678ce63c4170", size = 1771358, upload-time = "2025-11-10T02:03:33.331Z" }, + { url = "https://files.pythonhosted.org/packages/37/77/4297114d9e2fd2fc8ab0ab87192643cd49409eb059e2940391e7d2340e57/black-25.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dae49ef7369c6caa1a1833fd5efb7c3024bb7e4499bf64833f65ad27791b1545", size = 1612902, upload-time = "2025-11-10T01:59:33.382Z" }, + { url = "https://files.pythonhosted.org/packages/de/63/d45ef97ada84111e330b2b2d45e1dd163e90bd116f00ac55927fb6bf8adb/black-25.11.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bd4a22a0b37401c8e492e994bce79e614f91b14d9ea911f44f36e262195fdda", size = 1680571, upload-time = "2025-11-10T01:57:04.239Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4b/5604710d61cdff613584028b4cb4607e56e148801ed9b38ee7970799dab6/black-25.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:aa211411e94fdf86519996b7f5f05e71ba34835d8f0c0f03c00a26271da02664", size = 1382599, upload-time = "2025-11-10T01:57:57.427Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9a/5b2c0e3215fe748fcf515c2dd34658973a1210bf610e24de5ba887e4f1c8/black-25.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3bb5ce32daa9ff0605d73b6f19da0b0e6c1f8f2d75594db539fdfed722f2b06", size = 1743063, upload-time = "2025-11-10T02:02:43.175Z" }, + { url = "https://files.pythonhosted.org/packages/a1/20/245164c6efc27333409c62ba54dcbfbe866c6d1957c9a6c0647786e950da/black-25.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9815ccee1e55717fe9a4b924cae1646ef7f54e0f990da39a34fc7b264fcf80a2", size = 1596867, upload-time = "2025-11-10T02:00:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/1a3859a7da205f3d50cf3a8bec6bdc551a91c33ae77a045bb24c1f46ab54/black-25.11.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92285c37b93a1698dcbc34581867b480f1ba3a7b92acf1fe0467b04d7a4da0dc", size = 1655678, upload-time = "2025-11-10T01:57:09.028Z" }, + { url = "https://files.pythonhosted.org/packages/56/1a/6dec1aeb7be90753d4fcc273e69bc18bfd34b353223ed191da33f7519410/black-25.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:43945853a31099c7c0ff8dface53b4de56c41294fa6783c0441a8b1d9bf668bc", size = 1347452, upload-time = "2025-11-10T01:57:01.871Z" }, + { url = "https://files.pythonhosted.org/packages/00/5d/aed32636ed30a6e7f9efd6ad14e2a0b0d687ae7c8c7ec4e4a557174b895c/black-25.11.0-py3-none-any.whl", hash = "sha256:e3f562da087791e96cefcd9dda058380a442ab322a02e222add53736451f604b", size = 204918, upload-time = "2025-11-10T01:53:48.917Z" }, +] + +[[package]] +name = "black" +version = "26.5.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "click", version = "8.4.2", source = { registry = "https://pypi.org/simple" } }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs", version = "4.11.0", source = { registry = "https://pypi.org/simple" } }, + { name = "pytokens" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/37/5628dd55bf2b34257fc7603f0fe97c40e3aaf24265f416a9c85c95ca1436/black-26.5.1.tar.gz", hash = "sha256:dd321f668053961824bcc1be1cc1df748b2d7e4fa28086b08331e577b0100a73", size = 679439, upload-time = "2026-05-18T16:53:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/84/b3f55026206a9e8820a91503308075ca48eadc515e436731ca01dbe043b3/black-26.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9942db8888e06943c5dde66ca0037dcff82a2a4ec1ad0ada9e0d2ee9d9823893", size = 1987719, upload-time = "2026-05-18T17:05:02.757Z" }, + { url = "https://files.pythonhosted.org/packages/c6/34/7db312c5e5783d6e76cffd9d5ac8972a32badae4c6e3288dac0eed8d3bed/black-26.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:89c93167a74d3a75dfaa38a5c7cca015537d5820dd7f17d63267d674a61cae90", size = 1810083, upload-time = "2026-05-18T17:05:04.302Z" }, + { url = "https://files.pythonhosted.org/packages/33/e2/e0101e73c2c8727634e2efcb35e2b34bd23ad70dfa673789f5773a591b21/black-26.5.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f2cd76d069cc54c71f10360744ba8983fbb616903b4304a85b734915c8e1b4", size = 1860633, upload-time = "2026-05-18T17:05:06.391Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/e15c0c5b23cf3651035fe5addcce90e283af3548a3f91bb03d81b83106ab/black-26.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:87ed5c6f450580a2f6790bc7cbfb016dfc73bc750249762268a3695361315eef", size = 1477886, upload-time = "2026-05-18T17:05:07.96Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3f/59d43ade98d2ce5c8dc34a4e46cbecd177e6d55d7d4092969c6003ccc655/black-26.5.1-cp310-cp310-win_arm64.whl", hash = "sha256:58b4bd92cf88aacf83d88479c8f9caee044b1ec55f2451a337354a7ea2590a22", size = 1277111, upload-time = "2026-05-18T17:05:09.473Z" }, + { url = "https://files.pythonhosted.org/packages/4b/96/3c3e09f09f44a37aac36b178a279cd19aa7001bd796187a7b162a294c81f/black-26.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:96ae2c733b2aabdd9986e2c5df628ff3473676cd1c5faded1ff496cf6d74083c", size = 1970639, upload-time = "2026-05-18T17:05:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/83/ea/5ad117b9ee3ecd933c712bcbae610006e5b7cc9f41c526cd7ed3b6c4124c/black-26.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0e48b87e03bf109288e55cfceadcfa15ff5470aca2851a851950ed2926f450d7", size = 1792130, upload-time = "2026-05-18T17:05:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/06/3a/7c448bc623fcdfa96672531beb5a616ea5e64f6975955254d7731ffb0ad9/black-26.5.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5119fa92ae61f786e8c3662fd60aece1d0a2dd5cca5d0c79417a95e7a4272a59", size = 1846134, upload-time = "2026-05-18T17:05:14.506Z" }, + { url = "https://files.pythonhosted.org/packages/a1/5b/0b39b3a5917f0657ac014ad2edb58c139553a478adfe7f817abf1622ff6e/black-26.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:30d3c14661f2792e9142cce3eeeb1cbc175b3eb5f733be0c8eeb99651e52b0c3", size = 1478883, upload-time = "2026-05-18T17:05:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/4c/48/dc222692e0f95030db1bbfb6c857e76858bad09058221ea7aae815255327/black-26.5.1-cp311-cp311-win_arm64.whl", hash = "sha256:1ef92b76f7733f282fd096ea406200b5a286c42947412b0eaff3a74e3616cefe", size = 1277776, upload-time = "2026-05-18T17:05:18.029Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/7744b906703228264ef73bdd534df88ec1ef3de45c4e78f6d31b9e32d0c9/black-26.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4ad6fa01f941920f54f2bbb35f3df7673428a0ef98a0b0840c2eaef3b110efa8", size = 2012518, upload-time = "2026-05-18T17:05:20.108Z" }, + { url = "https://files.pythonhosted.org/packages/b7/c0/c5a3b1636dfd09c42534f2b3cf33506814f6d3e066fb0879ffa16c1ae860/black-26.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3915f256e75a2d7cf88d8953d37f780455dc586cc72dee059c528fe77f581217", size = 1816016, upload-time = "2026-05-18T17:05:21.84Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0e/36044316b65ca471d3bb6d3703fd06fb50c6b727c3562f6a5a3153634f88/black-26.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d98d4137277c75dfb898ec8d846c4fd68ba1e9cf77f95e2865c203dc18f4c3d", size = 1884150, upload-time = "2026-05-18T17:05:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/b3/33/dafc5808c2af43672912111d7c3354af1615f7e2be3bed7a878461abbe4d/black-26.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:a1dca32d9f1784af512a13410ec204c6f7f0aa9797a111c42e1c03449821c264", size = 1486825, upload-time = "2026-05-18T17:05:25.004Z" }, + { url = "https://files.pythonhosted.org/packages/82/14/b965ee6ad2a311f28bdbf692def3ee9848d2ae289dab28b27657fcee3e78/black-26.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:1037d5ac7b7b310b2632ad867ec8d0e4c4819dcdb0b820f63135da746a24e418", size = 1288646, upload-time = "2026-05-18T17:05:26.477Z" }, + { url = "https://files.pythonhosted.org/packages/3f/5c/c384363980e11e25ca6b93205949bb331fbf35f4e0dbec376dfa6326cec8/black-26.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b36cf2ddf5566e205f6535f782a62194a184d33e175b64ae8c40b1737522be3", size = 2009020, upload-time = "2026-05-18T17:05:28.132Z" }, + { url = "https://files.pythonhosted.org/packages/0b/df/9f31c5e0babbfed77d505fc5d120beb98b21b33feaeded3924ea941fe360/black-26.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f7ea64ebfa01b50f693508fc39f875e264446d3b097088f84f203b9d09618a0", size = 1813335, upload-time = "2026-05-18T17:05:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/fb/24/8e7b9a2fa61b0afd82209efe937557d180a1fa055bd7f6161eb9defc3719/black-26.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecb3e624844c798144e9bd986954e0adc81d8911a1f30f375e1252fe26e8c294", size = 1881614, upload-time = "2026-05-18T17:05:32.718Z" }, + { url = "https://files.pythonhosted.org/packages/49/ad/b4e0d9365ba8ac34f6bbab62a4b1b2dd5d618fac3fa1b8db968c844201b5/black-26.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:e1a26503279b6b310669fb0b219c39e4820b77e8189fe80f522bb511f247db0a", size = 1488925, upload-time = "2026-05-18T17:05:34.259Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4b/652b859bf5df88a751c30451b09338f7fd26a77d1271c666992f836b7711/black-26.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c34b25da232ead53a6f335b76dbea124f4d152ad568b9080d6f944bc2b34b52", size = 1289883, upload-time = "2026-05-18T17:05:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a8da8eb208c51c7f4ce74609a45d0dcc6d8a2141e45e81ee5289d1bb0d59/black-26.5.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e88976690a64b0af98312ca958415849cb42423423c5f2ee74af4b49a97a2168", size = 2004800, upload-time = "2026-05-18T17:05:38.182Z" }, + { url = "https://files.pythonhosted.org/packages/11/8a/a479296a19e383b70a725882a6cf3d786540601ff03cabbaaf1cce864c5a/black-26.5.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:32d5ea7f6c8bdfa6e648326ebca1f02b0764e2a029edc6f8dce2627e19d468c3", size = 1815576, upload-time = "2026-05-18T17:05:40.309Z" }, + { url = "https://files.pythonhosted.org/packages/81/6b/cfaf3d39f25132c156a068f6b805576c9103a84086019507c70e1911ee7d/black-26.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8d16dc41655aa113cd64665e7219446cd7e4ff2248d7178eaa905190c86b18", size = 1877927, upload-time = "2026-05-18T17:05:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/302e313964bcff7e28df329d39f84f5270095730d85ff0acc260610a0d82/black-26.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:577f21094ea469ef92ec1adaf2c9441a226d2144d01a5be2fa823cecf6543e50", size = 1511860, upload-time = "2026-05-18T17:05:43.943Z" }, + { url = "https://files.pythonhosted.org/packages/27/4e/a3827e35e0e567f9f9ee59e2a0ab979267dca98718f25547ca8c6733afd4/black-26.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:ed1a20af114c301a0269bf01163d51dbef72737fd65f850001e7cbe7f3c7abae", size = 1316632, upload-time = "2026-05-18T17:05:45.521Z" }, + { url = "https://files.pythonhosted.org/packages/94/51/f975cae76d44274cc2868dc9040ac5d58d464784610234455b4e7b19c6ef/black-26.5.1-py3-none-any.whl", hash = "sha256:4ed7f7da04046d2e488437170797d3b4a4ad83906683bcb7dfc68b673bbce5e2", size = 213693, upload-time = "2026-05-18T16:53:33.964Z" }, +] + +[[package]] +name = "certifi" +version = "2026.7.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112, upload-time = "2026-07-22T03:35:12.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983, upload-time = "2026-07-22T03:35:11.276Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/2a/23f34ec9d04624958e137efdc394888716353190e75f25dd22c7a2c7a8aa/charset_normalizer-3.4.9.tar.gz", hash = "sha256:673611bbd43f0810bec0b0f028ddeaaa501190339cac411f347ac76917c3ae7b", size = 152439, upload-time = "2026-07-07T14:34:58.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/81/8e983840c6e5b93b33c2ba81aa3d52c2e42f0e9a690ce7607a2e61da4a5c/charset_normalizer-3.4.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd6280cf040f233bd7d3407b743b4b4c74f70e8e1c4199cb112a62c941c0772a", size = 322240, upload-time = "2026-07-07T14:32:36.236Z" }, + { url = "https://files.pythonhosted.org/packages/de/d1/b4319dc3229d8272fba305e206fc0a148e2de8d4087917ce62ae6382f359/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa99adc8f081b475a12843953db36831eaf83ec33eb46a90629ca6a5de45a616", size = 216475, upload-time = "2026-07-07T14:32:38.142Z" }, + { url = "https://files.pythonhosted.org/packages/80/33/6c99c1b3e6b8bf730e1bc809b9a2608f224145069114c479a2e9e1494346/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c1225416b463483160e4af85d5fc3a9690ccb53fd4b1865a6437825f5ede3209", size = 238670, upload-time = "2026-07-07T14:32:39.658Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/ffbb83546e1f198ecc70ecd372b65cf2b50f9068b380abd67640f17a8e18/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16d10d789dd9bcca1173c95af82c58433122564b7bc39385124be735a35cbe99", size = 233476, upload-time = "2026-07-07T14:32:41.155Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5f/b98b8da398637b551e427e7be922bdec19177dc54d6811dcdaa503f23aac/charset_normalizer-3.4.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9bb41182d93ea91f60b4bc8fbf4c820c69ef8a12ab2d917f3f1834f1acad07e8", size = 223817, upload-time = "2026-07-07T14:32:42.592Z" }, + { url = "https://files.pythonhosted.org/packages/36/31/a276bb2e66243072a3fd06fdcab9cbb61a305b02143d70d2bda21d888fa8/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:bcf74c1df76758a395bf0af608c04c82257523f55c9868b334f06270d0f2112b", size = 207974, upload-time = "2026-07-07T14:32:44.258Z" }, + { url = "https://files.pythonhosted.org/packages/5e/be/7ee4453d7e88dfbc4104ccd34900b9f2c7c17dac22881865fe0e82424a25/charset_normalizer-3.4.9-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b5314963fce9b0b12743891de876e724997864ee22aa496f903f426c7e2fa5b2", size = 221655, upload-time = "2026-07-07T14:32:45.64Z" }, + { url = "https://files.pythonhosted.org/packages/1d/85/181c652953eb5276d198f375b1dd641047392050098100a3a02d6534f657/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9701d0049d92c16703a42771b98d560b95248949f23f8cf7b4eddd201814fb9", size = 219229, upload-time = "2026-07-07T14:32:47.376Z" }, + { url = "https://files.pythonhosted.org/packages/0c/e7/aaf6da33fc9f4691cda8f7efbc9f69179d3d39ec8a4799baf273ee1d8db0/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:65a7ff3f705e57d392f7261b6d0550fe137c3019477431f1c355e0db0a7d3e15", size = 209704, upload-time = "2026-07-07T14:32:48.855Z" }, + { url = "https://files.pythonhosted.org/packages/63/01/f2fb3bd3a73be48b173ee0c6aa8d2497af97d5663a8c4c4b491de4c62f7a/charset_normalizer-3.4.9-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79580094b00d1789d1f93ea55bc43cb2f611910c72235b7657f3482ddcc1b22d", size = 226243, upload-time = "2026-07-07T14:32:50.239Z" }, + { url = "https://files.pythonhosted.org/packages/c4/02/c57a22739fe05246b0b5783b3bfb6afaac4eebb46f3ececdfb2f048f780e/charset_normalizer-3.4.9-cp310-cp310-win32.whl", hash = "sha256:432786d3561e69aeeae6c7e8648964ce0ad05736120135601f87ac26b9c83381", size = 150935, upload-time = "2026-07-07T14:32:51.676Z" }, + { url = "https://files.pythonhosted.org/packages/37/8d/ca39a7559a4797505530d084fd3a49a2c959efbbbff146302fb7be4e3b35/charset_normalizer-3.4.9-cp310-cp310-win_amd64.whl", hash = "sha256:8c041122946b7ba21bb32c45b1aa57b1be35527690aeb3c5c234521085632eee", size = 162314, upload-time = "2026-07-07T14:32:53.193Z" }, + { url = "https://files.pythonhosted.org/packages/01/da/a44bd7a13d426e69e4894557106cd58669097bfad4a8681123b618fbfc5d/charset_normalizer-3.4.9-cp310-cp310-win_arm64.whl", hash = "sha256:375b83ed0aecfce76c16d198fbc21f3b11b337d68662bea0a995046682a11419", size = 153075, upload-time = "2026-07-07T14:32:54.554Z" }, + { url = "https://files.pythonhosted.org/packages/0b/e3/85ec501f206fb049259288c1f3506e53876937fb00edb47009348e66756b/charset_normalizer-3.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e94703ec9684807f20cfb5eed95c70f67f2a8f21ad620146d7b5a13677b93e5", size = 317075, upload-time = "2026-07-07T14:32:56.021Z" }, + { url = "https://files.pythonhosted.org/packages/c3/69/2a5385192e67175f7d8bd5ce4f57c24bc956439adeae5c13a99aa28a53d1/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a441ea71902098ffe78c5abe6c494f44160b4af614ed16c3d9a3b1d17fd8ee2", size = 213837, upload-time = "2026-07-07T14:32:57.78Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/03ddc7da576d814fe0a36dd1f0fd3258e95404b4b2e3c026b7923d7e133f/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:304b13570067b2547562e308af560b3963857b1fa90bd6afd978130130fe2d6a", size = 235503, upload-time = "2026-07-07T14:32:59.205Z" }, + { url = "https://files.pythonhosted.org/packages/4e/6e/de0229a7ef40f6f9d28a837eebf4ec47bdca5dab4e900c84f22919af636a/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4773092f8019072343a7447203308b176e10199920eb02d6195e81bbb3274c29", size = 229944, upload-time = "2026-07-07T14:33:00.803Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/49b9060e8418b14fb5cba9cf6bfb383111e2538a03a1fb18e66a95aeb3d5/charset_normalizer-3.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:04ce310cb89c15df659582aee80a0603788732a5e017d5bd5c81158106ce249c", size = 221276, upload-time = "2026-07-07T14:33:02.199Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/80282cce0fae9c3061203d723ee87da996aed79679e65d8935050ee7ca1f/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:c0323c9daef75ef2e5083624b4585018a0c9d5e3b40f607eed81a311270b934b", size = 205260, upload-time = "2026-07-07T14:33:03.698Z" }, + { url = "https://files.pythonhosted.org/packages/0c/74/2f62c8821b969ea3bd67cc2e6976834f48ca5d12664d2559ebcd9bcfbed7/charset_normalizer-3.4.9-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:871ff67ea1aad4dfd91736464934d56b32dac49f9fbe16cddba36198a7b3a0db", size = 217786, upload-time = "2026-07-07T14:33:05.12Z" }, + { url = "https://files.pythonhosted.org/packages/d9/8d/feabb82cb49fcad14515b1d7d1ca4787b0da7fc723a212bf89bc9e0fac52/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:67830fc78e67501f47bb950471b2dcb9b35b140084429318e862895a8e89c993", size = 216798, upload-time = "2026-07-07T14:33:06.629Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ff/c946d63bc3786d5b84d960b0f7ab7e25b828486a946b5aa997625bcaf6a6/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d92613ec25e43b05f042302531ec0f00b8445190e43325880cbd6ab7c2581da", size = 206429, upload-time = "2026-07-07T14:33:08.006Z" }, + { url = "https://files.pythonhosted.org/packages/af/ba/5e5007c370702f85d2ef75791fac7943ed41e080364a673b20142e430e3e/charset_normalizer-3.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:280081916dc341820640489a66e4696049401ef1cf6dd672f672e70ad915aca3", size = 223066, upload-time = "2026-07-07T14:33:09.783Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/9096aa3cf532dfad237861544eb47a0f20d5adbf1039760fed8eaae935d9/charset_normalizer-3.4.9-cp311-cp311-win32.whl", hash = "sha256:ac351b3b8014eead140e77e9717e2992c6bbe30b63bc3422422eb84865412e3d", size = 150456, upload-time = "2026-07-07T14:33:11.217Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a1/e29995109e455dc8eff8d0fac6ae509be39561318a7cfeac5d33ad029213/charset_normalizer-3.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:6366a16e1a25018694d6a5d784d09b046edc9eac40ea2b54065c3052672516a1", size = 161410, upload-time = "2026-07-07T14:33:12.743Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8d/1569f4d0032d6ba2a4fe4591c35bf87868c600c41a71eb5c2e1ffa8464c2/charset_normalizer-3.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:1d22856ffbe153a602df38e4a5464f0b748a54002e0d69ac6d2ad0a197cc99ec", size = 152649, upload-time = "2026-07-07T14:33:14.173Z" }, + { url = "https://files.pythonhosted.org/packages/70/4a/ecbd131485c07fcdfad54e28946d513e3da22ef3b4bd854dcafae54ec739/charset_normalizer-3.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45b0cc4e3556cd875e09102988d1ab8356c998b596c9fced84547c8138b487a0", size = 319300, upload-time = "2026-07-07T14:33:15.666Z" }, + { url = "https://files.pythonhosted.org/packages/ec/96/5d9364e3342d69f3a045e1777bc47c85c383e6e9466d561b33fdb419d1f9/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b2aff1c7b3884512b9512c3eaadd9bab39fb45042ffaaa1dd08ff2b9f8109d9", size = 215802, upload-time = "2026-07-07T14:33:17.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/4c/5361f9aa7f2cb58d94f2ab831b3d493f69efb1d239654b4744e3c09527cb/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9104ed0bd76a429d46f9ec0dbc9b08ad1d2dcdf2b00a5a0daa1c145329b35b44", size = 237171, upload-time = "2026-07-07T14:33:18.576Z" }, + { url = "https://files.pythonhosted.org/packages/50/78/ce342ca4ff30b2eb49fe6d9578df85974f90c67d294113e94efdd9664cbd/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7b86a2b16095d250c6f58b3d9b2eee6f4147754344f3dab0922f7c9bf7d226c9", size = 233075, upload-time = "2026-07-07T14:33:20.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/c4/4fa4c8b3097a11f3c5f09a35b72ed6855fb1d332469504962ab7bafcc702/charset_normalizer-3.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e226f6218febc71f6c1fc2fafb91c226f75bdc1d8fb12d66823716e891608fd", size = 224256, upload-time = "2026-07-07T14:33:21.747Z" }, + { url = "https://files.pythonhosted.org/packages/87/3a/ad914516df7e358a81aae018caa5e0470ba827fa6d763b1d2e87d920a5f6/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:90c44bc373b7687f6948b693cceaea1348ae0975d7474746559494468e3c1d84", size = 208784, upload-time = "2026-07-07T14:33:23.313Z" }, + { url = "https://files.pythonhosted.org/packages/d7/74/3c12f9755717dfe5c5c87da63f35d765fa0c00382ec26bf23f7fae34f2ba/charset_normalizer-3.4.9-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cdef90ae47919cae358d8ab15797a800ed41da7aba5d72419fb510729e2ed4b", size = 219928, upload-time = "2026-07-07T14:33:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/895095b83e7907abd6d3d99aad3a38ad0d9686cc186cb0c94c24320fe63e/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:60f44ade2cf573dad7a277e6f8ca9a51a21dda572b13bd7d8539bb3cd5dbedde", size = 218489, upload-time = "2026-07-07T14:33:26.42Z" }, + { url = "https://files.pythonhosted.org/packages/a1/34/ef5c05f412f42520d7709b7d3784d19640839eb7366ded1755511585429f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a1786910334ed46ab1dd73222f2cd1e05c2c3bb39f6dddb4f8b36fc382058a39", size = 210267, upload-time = "2026-07-07T14:33:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/83/dc/9b29fa4412b318bf3bfea985c35d67eb55e04b59a7c3f2237168b0e0be6f/charset_normalizer-3.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03d07803992c6c7bbc976327f34b18b6160327fc81cb82c9d504720ac0be3b62", size = 226030, upload-time = "2026-07-07T14:33:29.397Z" }, + { url = "https://files.pythonhosted.org/packages/0e/42/6dbc00b8cd16011691203e33570fa42ed5746599a2e878112d16eab403a3/charset_normalizer-3.4.9-cp312-cp312-win32.whl", hash = "sha256:78841cccf1af7b40f6f716338d50c0902dbe88d9f800b3c973b7a9a0a693a642", size = 151185, upload-time = "2026-07-07T14:33:30.781Z" }, + { url = "https://files.pythonhosted.org/packages/80/cc/f920afd1a23c58ccd53c1d36085a71893a4737ff5e66e0371efab6809850/charset_normalizer-3.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b3dac63058cc36820b0dd072f89898604e2d39686fe05321729d00d8ac185a0", size = 162557, upload-time = "2026-07-07T14:33:32.176Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/0386d43a261ff4e4b30c5857af7df877254b46bec7b9d1b74b6bf969a90b/charset_normalizer-3.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:78fa18e436a1a0e58dbd7e02fc4473f3f32cceb12df9dfca542d075961c307d2", size = 152665, upload-time = "2026-07-07T14:33:33.711Z" }, + { url = "https://files.pythonhosted.org/packages/b2/06/97ec2aeae780b31d742b6352218b43841a6871e2564578ca522dce4a45c3/charset_normalizer-3.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:440eede837960000d74978f0eba527be106b5b9aee0daf779d395276ed0b0614", size = 317688, upload-time = "2026-07-07T14:33:35.408Z" }, + { url = "https://files.pythonhosted.org/packages/d0/39/8ff066c672434225f8d25f8b739f992af250944392173dcc88362681c9bf/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21e764fd1e70b6a3e205a0e46f3051701f98a8cb3fad66eeb80e48bb502f8698", size = 214982, upload-time = "2026-07-07T14:33:36.996Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/3a47a3667c83c2df9483d91644c6c107de3bf8874aa1793da9d3012eb986/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e4fd89cc178bced6ad29cb3e6dd4aa63fa5017c3524dbd0b25998fb64a87cc8b", size = 236460, upload-time = "2026-07-07T14:33:38.536Z" }, + { url = "https://files.pythonhosted.org/packages/f1/60/b22cdbee7e4013dab8b0d7647fc6181120fbbbc8f7025c226d15bd5a47fc/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bd47ba7fc3ca94896759ea0109775132d3e7ab921fbf54038e1bab2e46c313c9", size = 232003, upload-time = "2026-07-07T14:33:40.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f8/72eb13dcabe7257035cea8aefd922caad2f110d252bf9f67c4c2ca763aee/charset_normalizer-3.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:84fd18bcc17526fc2b3c1af7d2b9217d32c9c04448c16ec693b9b4f1985c3d33", size = 223149, upload-time = "2026-07-07T14:33:41.631Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3e/faee8f9de92b14ee1198e9163252bb15efee7301b31256a3b6d9ebfdd0dd/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:5b10cd92fc5c498b35a8635df6d5a100207f88b63a4dc1de7ef9a548e1e2cd63", size = 207901, upload-time = "2026-07-07T14:33:43.209Z" }, + { url = "https://files.pythonhosted.org/packages/3a/25/45f30093ae27dd7b92a793b61882a38685f993700113ca36e0c9c14965e1/charset_normalizer-3.4.9-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a4fbdde9dd4a9ce5fd52c2b3a347bb50cc89483ef783f1cb00d408c13f7a96c0", size = 219176, upload-time = "2026-07-07T14:33:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/48/18/c8f397329c35e32f6a837e488986f4ae03bd2abebc453b48714991630c2f/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:416c229f77e5ea25b3dfd4b582f8d73d7e43c22320302b9ab128a2d3a0b38efe", size = 217356, upload-time = "2026-07-07T14:33:46.192Z" }, + { url = "https://files.pythonhosted.org/packages/86/7e/5ce0bba863470fd1902d5e5843968951bddf38abe4742fc97116ef4598b3/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:75286256590a6320cf106a0d28970d3560aad9ee09aa7b34fb40524792436d35", size = 209614, upload-time = "2026-07-07T14:33:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/6c/ef/2473d3c4d869155be4af1191111d59c4d5c4e0173026f7e85b176e23bf65/charset_normalizer-3.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69b157c5d3292bcd443faca052f3096f637f1e074b98212a933c074ae23dc3b8", size = 224991, upload-time = "2026-07-07T14:33:49.238Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a3/53ddae3db108a088156aa8ddfafd411ebbc1340f48c5573f697b27f69a39/charset_normalizer-3.4.9-cp313-cp313-win32.whl", hash = "sha256:51307f5c71007673a2bf8232ad973483d281e74cb99c8c5a990af1eefa6277d9", size = 150622, upload-time = "2026-07-07T14:33:50.711Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/6953a77c7cf2c2ff9998e6f575ab3e380119f100223381565a4f94c1f836/charset_normalizer-3.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:fe2c7201c642b7c308f1675355ad7ff7b66acfe3541625efe5a3ad38f29d6115", size = 161947, upload-time = "2026-07-07T14:33:52.197Z" }, + { url = "https://files.pythonhosted.org/packages/6e/fb/d560d1d1555debbfe7849d9cac6145c1b537709d79576bf22557ed803b82/charset_normalizer-3.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:611057cc5d5c0afc743ba8be6bd828c17e0aaa8643f9d0a9b9bb7dea80eb8012", size = 152594, upload-time = "2026-07-07T14:33:53.486Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8d/496817fa0944239ecae662dd57ea765cfeaec6a735f9f025d4b7b72e7143/charset_normalizer-3.4.9-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0327fcd59a935777d83410750c50600ee9571af2846f71ce40f25b13da1ef380", size = 317253, upload-time = "2026-07-07T14:33:54.994Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f9/ef4a69ea338ad3c0deceea0f5f7d2380ae8b52132b06d652cb0d2cd86706/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a79d9f4d8001473a30c163556b3c3bfebec837495a412dde78b51672f6134f9", size = 215898, upload-time = "2026-07-07T14:33:56.334Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e7/5ddfd76fc061eb52de219658a4aa431cbacadf0a0219c8854f00da50d289/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:33bdcc2a32c0a0e861f60841a512c8acc658c87c2ac59d89e3a46dacf7d866e4", size = 236718, upload-time = "2026-07-07T14:33:57.9Z" }, + { url = "https://files.pythonhosted.org/packages/49/ba/768fa3f36048d81c477a0ce61f813bc1454d80917ccfe550abd9f44f5e24/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f840ed6d8ecba8255df8c42b87fadeda98ddfc6eeec05e2dc66e26d46dd6f58a", size = 232519, upload-time = "2026-07-07T14:33:59.811Z" }, + { url = "https://files.pythonhosted.org/packages/f4/c4/b3e049d2aa3766180c78507110543d9d50894cc97f57de543f1be521dcdc/charset_normalizer-3.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c25fe15c70c59eb7c5ce8c06a1f3fa1da0ecc5ea1e7a5922c40fd2fa9b0d5046", size = 223143, upload-time = "2026-07-07T14:34:01.517Z" }, + { url = "https://files.pythonhosted.org/packages/19/79/55c32d06d76ae4feafe053f061f3e3ab70bcf19f4007797ce8c3efda7830/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:f7fb7d750cfa0a070d2c24e831fd3481019a60dd317ea2b39acbcebc08b6ed81", size = 206742, upload-time = "2026-07-07T14:34:03.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/e0/47c079dd82d217c807479cd59ffd30af56307ea31c108b75758970459ad3/charset_normalizer-3.4.9-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d1c96a7a18b9690a4d46df09e3e3382406ae3213727cd1019ebade1c4a81917", size = 219191, upload-time = "2026-07-07T14:34:04.657Z" }, + { url = "https://files.pythonhosted.org/packages/42/ab/b9bc2e77d6b44a7e46ef62ec5cac1c9a6ba7b9135a5d560f002696ec9995/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a4cfde78a9f2880208d16a93b795726a3017d5977e08d1e162a7a31322479c41", size = 218328, upload-time = "2026-07-07T14:34:06.115Z" }, + { url = "https://files.pythonhosted.org/packages/f1/78/c9c71d599f5aa2d42bcdd35cbbd46d7f535351a57e40ff7d8e5a7e219401/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4d6fcde76f94f5cb9e43e9e9a61f16dacefd228cbbf6f1a09bd9b219a92f1a1", size = 207406, upload-time = "2026-07-07T14:34:07.554Z" }, + { url = "https://files.pythonhosted.org/packages/f6/39/c914445c321a845097ce4f6ac7de9a18228a77b766272125a1ce00d851eb/charset_normalizer-3.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:898f0e9068ca27d37f8e83a5b962821df851532e6c4a7d615c1c033f9da6eedf", size = 225157, upload-time = "2026-07-07T14:34:09.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f2/c0d4b8508565a36bc5c624e88ed297f5b0b1095011034d7f5b83a69908b5/charset_normalizer-3.4.9-cp314-cp314-win32.whl", hash = "sha256:c1c948747b03be832dceed96ca815cef7360de9aa19d37c730f8e3f6101aca48", size = 151095, upload-time = "2026-07-07T14:34:10.901Z" }, + { url = "https://files.pythonhosted.org/packages/49/fd/a1d26144398c67486422a72bf5812cda22cb4ccfcd95a290fb41ceb4b8e2/charset_normalizer-3.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:16b65ea0f2465b6fb52aa22de5eca612aa964ddfec00a912e26f4656cbef890b", size = 162796, upload-time = "2026-07-07T14:34:12.47Z" }, + { url = "https://files.pythonhosted.org/packages/20/95/d75e82f8ce9fd323ebf059c16c9aadefb22a1ecde13b7840b35835e4886c/charset_normalizer-3.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:40a126142a56b2dfc0aacbad1de8310cbf60da7656db0e6b16eebd48e3e93519", size = 153334, upload-time = "2026-07-07T14:34:14.044Z" }, + { url = "https://files.pythonhosted.org/packages/00/5e/17398df3a139985ba9d11ed072531986f408c8fca952835ef1ab1820c02b/charset_normalizer-3.4.9-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:609b3ba8fcc0fb5ab7af00719d0fb6ad0cb518e48e7712d12fd68f1327951198", size = 338848, upload-time = "2026-07-07T14:34:15.688Z" }, + { url = "https://files.pythonhosted.org/packages/cd/91/7253a32e86b7e1d1239b1b36ba6dd0f021a21107ab33054b53119cc083b9/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51447e9aa2684679af07ca5021c3db526e0284347ebf4ffcec1154c3350cfe32", size = 223022, upload-time = "2026-07-07T14:34:17.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/2e64bd2be10e89c61e57ebe6a93fd98ae88eb7ebe414b5121f22c96c69eb/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cc1b0fff8ead343dae06305f954eb8468ba0ec1a97881f42489d198e4ce3c632", size = 241590, upload-time = "2026-07-07T14:34:18.813Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ef/d96ec496cfea0c21db43b0ad03891308b02388d054cc902cf0e5a1ad6a88/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fa36ec09ef71d158186bc79e359ff5fdd6e7996fe8ab638f00d6b93139ba4fcf", size = 239584, upload-time = "2026-07-07T14:34:20.52Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ce/9af95f7876194bd7a14e3dfe4a4de2e0bff02666a3910d72beafd06cc297/charset_normalizer-3.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:df115d4d83168fdf2cae48ef1ff6d1cb4c466364e30861b37121de0f3bf1b990", size = 230224, upload-time = "2026-07-07T14:34:22.189Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/af74dde74a3996bd959c350709bfe50e297823d70a8c1cbd54b838880863/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:f86c6358749bd4fda175388691e3ba8c46e24c5347d0afd20f9b7edfc9faf07d", size = 212667, upload-time = "2026-07-07T14:34:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f0/f1c4fe746c395922961b5916ed1d7d6e7d4c84851d19ed43cc89980ec953/charset_normalizer-3.4.9-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:32286a2c8d167e897177b673176c1e3e00d4057caf5d2b64eef9a3666b03018e", size = 227179, upload-time = "2026-07-07T14:34:25.586Z" }, + { url = "https://files.pythonhosted.org/packages/e4/56/6c745619ac397e8871e2bcd3cea1eec86b877488f33888b3aef5c3ed506e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:83aed2c10721ddd90f68140685391b50811a880af20654c59af6b6c66c40513c", size = 225372, upload-time = "2026-07-07T14:34:27.212Z" }, + { url = "https://files.pythonhosted.org/packages/78/ad/98aae8630ac71f16711968e38a5acfecce41b778bf2f0312851020f565a8/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:cd6c3d4b783c556fa00bf540854e42f135e2f256abd29669fcd0da0f2dec79c2", size = 215222, upload-time = "2026-07-07T14:34:28.774Z" }, + { url = "https://files.pythonhosted.org/packages/f7/40/9593d54209765207a7f11073c06494c1721e4ca4a0a426c597679bf7f91e/charset_normalizer-3.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ee2f2a527e3c1a6e6411eb4209642e138b544a2d72fe5d0d76daf77b24063534", size = 231958, upload-time = "2026-07-07T14:34:30.345Z" }, + { url = "https://files.pythonhosted.org/packages/b1/27/693ee5e8a18191eb38647360c51cd505013e2bd3b366aa43fd5344c21e3c/charset_normalizer-3.4.9-cp314-cp314t-win32.whl", hash = "sha256:0d861473f743244d349b50f850d10eb87aeb22bbdcc8e64f79273c94af5a8226", size = 155580, upload-time = "2026-07-07T14:34:31.884Z" }, + { url = "https://files.pythonhosted.org/packages/80/3f/bd97d3d9c613013d07cb7733d299385b41df37f0471310f5a73dc359f0b8/charset_normalizer-3.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:9b8e0f3107e2200b76f6054de99016eac3ee6762713587b36baaa7e4bd2ae177", size = 167620, upload-time = "2026-07-07T14:34:33.438Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c6/eee9dca4439b1061f76373f06ea855678cc4a64c1c3c90b50e479edbb8eb/charset_normalizer-3.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:19ac87f93086ce37b86e098888555c4b4bc48102279bae3350098c0ed664b501", size = 158037, upload-time = "2026-07-07T14:34:35.018Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ec/81e22253f4b7091eca6515bb3da5e45d05a663f7f567bb745695dc60f892/charset_normalizer-3.4.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:253a4a220747e8b5faf57ec320c4f5efb0cef05f647420bf267143ec15dba10a", size = 306122, upload-time = "2026-07-07T14:34:36.607Z" }, + { url = "https://files.pythonhosted.org/packages/c8/53/a8c042eb9eee4716f4d42a0f5a571eb32a09ec429be9fb0b8b9d765393ba/charset_normalizer-3.4.9-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:68ce9f4d6b26d5ccbf7fd4459bf75f74a0a146677ebba80597df60cbdb20e6f4", size = 206284, upload-time = "2026-07-07T14:34:38.166Z" }, + { url = "https://files.pythonhosted.org/packages/14/cb/1db8b96547ee3186cd2dd7f2e59dd560a9b80748f3604171f3c153d62811/charset_normalizer-3.4.9-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:58150c9f9b9a552505912d182ccdf26f6396fb6094816ceebcbb20eecabaed94", size = 226837, upload-time = "2026-07-07T14:34:39.77Z" }, + { url = "https://files.pythonhosted.org/packages/6a/05/c94d5cd23396289c54c93b02e0273b4dd8921641d9968c4828caf9bbaad9/charset_normalizer-3.4.9-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df7276909358e5635ae203673ab7e509ddd224225a8d6b0790bf13eb2bde1cc5", size = 222199, upload-time = "2026-07-07T14:34:41.391Z" }, + { url = "https://files.pythonhosted.org/packages/6d/46/79847edd07244a4a2d443c6655a7b6ee94203c21539414b059f32713c357/charset_normalizer-3.4.9-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c09a49d6cde137258beb3d551994a2927fd35ad5cf96aed573f61bbd67c5f84", size = 214344, upload-time = "2026-07-07T14:34:42.986Z" }, + { url = "https://files.pythonhosted.org/packages/ec/b4/ef5a49b2e77c00deb43bb3256592b115ba9e4346016e82c516b8d215bf68/charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:231ddcbb35e2ff8973e1365db41fe0572662893b99a05deb183b68ad4c0c8bd4", size = 199988, upload-time = "2026-07-07T14:34:44.685Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ca/ad1d7c7d3077dab873f539d3e1d083c0845a762cb0bafdfbe3ef93add598/charset_normalizer-3.4.9-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:920079c3f7456fa213e0829ed2073aaa727fd39d889ead5b4f35d0de5460d04f", size = 211908, upload-time = "2026-07-07T14:34:46.227Z" }, + { url = "https://files.pythonhosted.org/packages/ed/61/710738687f90d01c06a04ed52d6ca1e62dd9b1d8cc2567098167c4691034/charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0fa1aec2d32bcc03c8fa0f6f1712caad1adc38509f31142112e5c9daf5b9c833", size = 209320, upload-time = "2026-07-07T14:34:47.753Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c0/6eec7bdabe6cbbcc274ec04596f6d93865751a0541d33d60d1ce179bd372/charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ad41ba96094304aa090f5a30cb6e4fb3b3f1c264c523394b4c39bbacc4dc92ba", size = 200980, upload-time = "2026-07-07T14:34:49.362Z" }, + { url = "https://files.pythonhosted.org/packages/eb/78/59344ff9a4a7b5f6530bf7bec2c980047cc42c3a616596cdbd8cb5c1a1af/charset_normalizer-3.4.9-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:43b9e366a31fdd1c87d0eb08f579b4a82b723ea54338f040d6b4e518a026ea29", size = 216545, upload-time = "2026-07-07T14:34:50.98Z" }, + { url = "https://files.pythonhosted.org/packages/17/6d/bff78a4bacc4891bc63ec5bdc6776d8c85e47fab93d0d5f6223068fad0a4/charset_normalizer-3.4.9-cp39-cp39-win32.whl", hash = "sha256:93d59d504b230e83c7a843251681959a0b6a9cd76f6e146ce1b8a80eb8739af9", size = 146256, upload-time = "2026-07-07T14:34:52.509Z" }, + { url = "https://files.pythonhosted.org/packages/a2/55/86048bde1c9d0352940bd7b87d825091a52aef67d01cde6c6f7342c5b552/charset_normalizer-3.4.9-cp39-cp39-win_amd64.whl", hash = "sha256:ddf4af30b417d9fe16481e9b81c27ab2a7cde1ff7ba3e85653b02db7d145dc7b", size = 156413, upload-time = "2026-07-07T14:34:54.117Z" }, + { url = "https://files.pythonhosted.org/packages/28/e9/9fb6099b868c82a40698a748ae0fbd4f31ccc13844c176a07158ba2abbfd/charset_normalizer-3.4.9-cp39-cp39-win_arm64.whl", hash = "sha256:476743fe6dfe14a2da12e3ac79125dc84a3b2cf8094369a47a1529b0cd8549fe", size = 147887, upload-time = "2026-07-07T14:34:55.51Z" }, + { url = "https://files.pythonhosted.org/packages/98/2b/f97f1c193fb855c345d678f5077d6926034db0722df74c8f057020e05a25/charset_normalizer-3.4.9-py3-none-any.whl", hash = "sha256:68e5f26a1ad57ded6d1cfb85331d1c1a195314756471d97758c48498bb4dcdf5", size = 64538, upload-time = "2026-07-07T14:34:56.993Z" }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, +] + +[[package]] +name = "click" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "decorator" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/60/8b/32f9823da46cde7df2087faa08cd98d01b908f8dcab982cdba9c84e85355/decorator-5.3.1.tar.gz", hash = "sha256:4cbcdd55a6efadb9dbea26b858f4fb3264567b52d69ca0d25b721b553f60ea82", size = 58084, upload-time = "2026-05-18T06:03:28.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/7f/798705f5296a58ca505d600456748d1be48078eac8a7050d8a98bc9edb89/decorator-5.3.1-py3-none-any.whl", hash = "sha256:f47fe6fdbd2edd623ecfe36875d37aba411624e2670dd395dddae1358689bb3c", size = 10365, upload-time = "2026-05-18T06:03:26.517Z" }, +] + +[[package]] +name = "distlib" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/02/bd72be9134d25ed783ecbbc38a539ffaefbf90c78418c7fb7229600dbac7/distlib-0.4.3.tar.gz", hash = "sha256:f152097224a0ae24be5a0f6bae1b9359af82133bce63f98a95f86cae1aede9ed", size = 615141, upload-time = "2026-06-12T08:04:52.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/08/9c41fb51ab5b43eb21674aff13df270e8ba6c4b29c8624e328dc7a9482af/distlib-0.4.3-py2.py3-none-any.whl", hash = "sha256:4b0ce306c966eb73bc3a7b6abad017c556dadd92c44701562cd528ac7fde4d5b", size = 470628, upload-time = "2026-06-12T08:04:50.506Z" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "executing" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/28/c14e053b6762b1044f34a13aab6859bbf40456d37d23aa286ac24cfd9a5d/executing-2.2.1.tar.gz", hash = "sha256:3632cc370565f6648cc328b32435bd120a1e4ebb20c77e3fdde9a13cd1e533c4", size = 1129488, upload-time = "2025-09-01T09:48:10.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, +] + +[[package]] +name = "filelock" +version = "3.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, +] + +[[package]] +name = "filelock" +version = "3.32.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/80/8232b582c4b318b817cf1274ba74976b07b34d35ef439b3eb948f98645a1/filelock-3.32.0.tar.gz", hash = "sha256:7be2ad23a14607ccc71808e68fe30848aeace7058ace17852f68e2a68e310402", size = 213757, upload-time = "2026-07-21T13:17:42.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/79/b4c714bef36bc4ec2beeae1e0c124f0223888cd8c6feb1cdc56038116920/filelock-3.32.0-py3-none-any.whl", hash = "sha256:d396bea984af47333ef05e50eae7eff88c84256de6112aea0ec48a233c064fe3", size = 97732, upload-time = "2026-07-21T13:17:41.55Z" }, +] + +[[package]] +name = "ghp-import" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, +] + +[[package]] +name = "identify" +version = "2.6.15" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, +] + +[[package]] +name = "identify" +version = "2.6.19" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/52/63/51723b5f116cc04b061cb6f5a561790abf249d25931d515cd375e063e0f4/identify-2.6.19.tar.gz", hash = "sha256:6be5020c38fcb07da56c53733538a3081ea5aa70d36a156f83044bfbf9173842", size = 99567, upload-time = "2026-04-17T18:39:50.265Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/84/d9273cd09688070a6523c4aee4663a8538721b2b755c4962aafae0011e72/identify-2.6.19-py2.py3-none-any.whl", hash = "sha256:20e6a87f786f768c092a721ad107fc9df0eb89347be9396cadf3f4abbd1fb78a", size = 99397, upload-time = "2026-04-17T18:39:49.221Z" }, +] + +[[package]] +name = "idna" +version = "3.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, +] + +[[package]] +name = "importlib-metadata" +version = "8.7.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/49/3b30cad09e7771a4982d9975a8cbf64f00d4a1ececb53297f1d9a7be1b10/importlib_metadata-8.7.1.tar.gz", hash = "sha256:49fef1ae6440c182052f407c8d34a68f72efc36db9ca90dc0113398f2fdde8bb", size = 57107, upload-time = "2025-12-21T10:00:19.278Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "ipython" +version = "8.18.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { name = "jedi", version = "0.19.2", source = { registry = "https://pypi.org/simple" } }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/b9/3ba6c45a6df813c09a48bac313c22ff83efa26cbb55011218d925a46e2ad/ipython-8.18.1.tar.gz", hash = "sha256:ca6f079bb33457c66e233e4580ebfc4128855b4cf6370dddd73842a9563e8a27", size = 5486330, upload-time = "2023-11-27T09:58:34.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/6b/d9fdcdef2eb6a23f391251fde8781c38d42acd82abe84d054cb74f7863b0/ipython-8.18.1-py3-none-any.whl", hash = "sha256:e8267419d72d81955ec1177f8a29aaa90ac80ad647499201119e2f05e99aa397", size = 808161, upload-time = "2023-11-27T09:58:30.538Z" }, +] + +[[package]] +name = "ipython" +version = "8.39.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "exceptiongroup" }, + { name = "jedi", version = "0.20.0", source = { registry = "https://pypi.org/simple" } }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/18/f8598d287006885e7136451fdea0755af4ebcbfe342836f24deefaed1164/ipython-8.39.0.tar.gz", hash = "sha256:4110ae96012c379b8b6db898a07e186c40a2a1ef5d57a7fa83166047d9da7624", size = 5513971, upload-time = "2026-03-27T10:02:13.94Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/56/4cc7fc9e9e3f38fd324f24f8afe0ad8bb5fa41283f37f1aaf9de0612c968/ipython-8.39.0-py3-none-any.whl", hash = "sha256:bb3c51c4fa8148ab1dea07a79584d1c854e234ea44aa1283bcb37bc75054651f", size = 831849, upload-time = "2026-03-27T10:02:07.846Z" }, +] + +[[package]] +name = "ipython" +version = "9.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "decorator" }, + { name = "ipython-pygments-lexers" }, + { name = "jedi", version = "0.20.0", source = { registry = "https://pypi.org/simple" } }, + { name = "matplotlib-inline" }, + { name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "prompt-toolkit" }, + { name = "psutil", marker = "sys_platform != 'cygwin' and sys_platform != 'emscripten'" }, + { name = "pygments" }, + { name = "stack-data" }, + { name = "traitlets" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/59/165d3b4d75cc34add3122c4417ecb229085140ac573103c223cd01dde96f/ipython-9.15.0.tar.gz", hash = "sha256:da2819ce2aa83135257df830660b1176d986c3d2876db24df01974fa955b2756", size = 4442580, upload-time = "2026-06-26T11:03:35.913Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/3a/948263ca3b9d65bb2b1b0c521b3a49fad5d59ada58724bd87d2bd5ff3f36/ipython-9.15.0-py3-none-any.whl", hash = "sha256:515ad9c3cdf0c932a5a9f6245419e8aba706b7bd03c3e1d3a1c83d9351d6aa6e", size = 630895, upload-time = "2026-06-26T11:03:33.809Z" }, +] + +[[package]] +name = "ipython-pygments-lexers" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ef/4c/5dd1d8af08107f88c7f741ead7a40854b8ac24ddf9ae850afbcf698aa552/ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81", size = 8393, upload-time = "2025-01-17T11:24:34.505Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074, upload-time = "2025-01-17T11:24:33.271Z" }, +] + +[[package]] +name = "jedi" +version = "0.19.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/3a/79a912fbd4d8dd6fbb02bf69afd3bb72cf0c729bb3063c6f4498603db17a/jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0", size = 1231287, upload-time = "2024-11-11T01:41:42.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9", size = 1572278, upload-time = "2024-11-11T01:41:40.175Z" }, +] + +[[package]] +name = "jedi" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "parso" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/b7/a3635f6a2d7cf5b5dd98064fc1d5fbbafcb25477bcea204a3a92145d158b/jedi-0.20.0.tar.gz", hash = "sha256:c3f4ccbd276696f4b19c54618d4fb18f9fc24b0aef02acf704b23f487daa1011", size = 3119416, upload-time = "2026-05-01T23:38:47.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/93/242e2eab5fe682ffcb8b0084bde703a41d51e17ee0f3a31ff0d9d813620a/jedi-0.20.0-py2.py3-none-any.whl", hash = "sha256:7bdd9c2634f56713299976f4cbd59cb3fa92165cc5e05ea811fb253480728b67", size = 4884812, upload-time = "2026-05-01T23:38:43.919Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "librt" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/2f/3908645ddddab7120b46295e541ead308109fa48dbec7d67d7a778870d60/librt-0.13.0.tar.gz", hash = "sha256:1d2a610c14ac0d0750ee0a3ab8548e83155258387891caaca04def4bf7289781", size = 211402, upload-time = "2026-07-08T12:26:29.834Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/2f/ec5241c38e7fa0fe6c26bfc450e78b9489a6c3c08b394b85d2c10e506975/librt-0.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:34e47058fcc69a313293d6dee94216a4f30c929ae6f2476e58c5ba635aa639d5", size = 148654, upload-time = "2026-07-08T12:24:30.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1a/d651e18d3ee7aa2879322368c4f278bb7ecaa6b90caadfdec4ebfa8389f3/librt-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dbdd5b6509d0c2a8fe72cf494c299a61dbd58142a90a4190664ae159e4a7b547", size = 153537, upload-time = "2026-07-08T12:24:31.773Z" }, + { url = "https://files.pythonhosted.org/packages/45/18/10bff2122577246009d9619b6569596daf69b7648812f997ca9ca0426f60/librt-0.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e56ea4ee4df77585a6b5c138f6538680886024fa559f5b55bd14b12e98e67b2", size = 494336, upload-time = "2026-07-08T12:24:33.079Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/87dfee871b852970f137fdeae8e2ca356c5ab38e6f21d2a3299535fc3159/librt-0.13.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f1f9cc4d09a46d9cb3c2063ae100629d3f52a6517c3c08c2f4c9828261883929", size = 485393, upload-time = "2026-07-08T12:24:34.324Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d5/625447a8c0441ff5f15f4ac5e1d323fb9d4d256ebfde7a3c8e003f646057/librt-0.13.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f125f5d46b20f89dc5587a55cc416b4ba2a5b2ffda36d048ee120e17598a653a", size = 515382, upload-time = "2026-07-08T12:24:35.575Z" }, + { url = "https://files.pythonhosted.org/packages/8d/d8/1c8c49ea04235960426444deece9092a6b3a9587a850a81bae2335317411/librt-0.13.0-cp310-cp310-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2608d3b39f9e0b4a66a130d9150c615cba40a5090d25eeeaa225e0e46de8c0ac", size = 509483, upload-time = "2026-07-08T12:24:36.923Z" }, + { url = "https://files.pythonhosted.org/packages/6f/65/f1760fc48050e215201a03506c32b7270159088d01f64557b53e39e74a45/librt-0.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9fd35e95ab5e45c3901d37110263c7db85a961110f5460588fe37f8c131f88a7", size = 532503, upload-time = "2026-07-08T12:24:38.203Z" }, + { url = "https://files.pythonhosted.org/packages/18/1b/793e281dcf494879eff99f642b63ebc9c7c58694a1c2d1e93362a22c7041/librt-0.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5f31b0aa13c9b04370d4da6be1ab7779776b3a075cceb6747a39a4be85fe1e40", size = 537027, upload-time = "2026-07-08T12:24:39.34Z" }, + { url = "https://files.pythonhosted.org/packages/69/45/0801bbb40c9eea795d3dd3ce91c4c5f3fe7d42d23ec4be3e8cb283bcc754/librt-0.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0b795f5fc70fbbb787ceaf79bb3a0d627bcc33c53de51741755263ec406b775a", size = 517100, upload-time = "2026-07-08T12:24:40.907Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6c/eb5f514f8e29d4924bc0ff4601dd7b4175557e182e7c0721e84cffa39b8a/librt-0.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36b306a623aaad96fe4b378692b54f9c0789fccd833b9851753d5fbf6138cfde", size = 558653, upload-time = "2026-07-08T12:24:42.359Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bf/f140100d1b59fe87ff40b5ecbb4e27924335b189a784e230ee465452f6c2/librt-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a3762e75fcac8c9e4dacaaf438bffd9003e2ca2c531b756f3c0035deefa674c8", size = 104402, upload-time = "2026-07-08T12:24:43.668Z" }, + { url = "https://files.pythonhosted.org/packages/22/7c/57e40fef7cfb61869341cb28bdcefe8a950bebcbecca74a397bae14dce4a/librt-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:d63bae12a8aeb51380be3438e4dc4bd27354d0f8e19166b2f44e3e94d6f552dc", size = 125002, upload-time = "2026-07-08T12:24:44.793Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/a6498964cfeec270c468cffdc118f69c29b412593610d55fa1327ca51ff4/librt-0.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1b5a7bbff495baedbd9b916c367d66854008f8f3b575908ded477c499dc60082", size = 148029, upload-time = "2026-07-08T12:24:45.961Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/dc86d1bffd8e0c2818bace29d9f7783cfbb8e0673bf3673b5bbd5bbe0420/librt-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34bc7938b9fdf14fe32a406c19c71faf894c5cee7e7474bd0be2f17200b82d14", size = 153036, upload-time = "2026-07-08T12:24:47.257Z" }, + { url = "https://files.pythonhosted.org/packages/29/3f/b923826660f02f286186cd9303d52bb05ced0a13708edc104dc8480920e3/librt-0.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f40e56b61b41be5f7dec938cfeffd660668cf4b5e72c78e7bd671d66b7bc2c79", size = 493062, upload-time = "2026-07-08T12:24:48.483Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/6c0980a9c9b1302cb68d108906697b89eceb55889bb1dcf77c109aa56ca5/librt-0.13.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:9c5d02b89de5acd0379a51ec44a89476fb03df6145442e1c8ecd6bee2f91b176", size = 485510, upload-time = "2026-07-08T12:24:49.727Z" }, + { url = "https://files.pythonhosted.org/packages/32/81/795ae3b9df5dd94079fb807e38191855e023e8c6249014ae6bc3f0d9a490/librt-0.13.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7db9a3ff32ef5f7d1703d93831a3316cdf0b537de6a1cc03cc8fdd09b9194e89", size = 515909, upload-time = "2026-07-08T12:24:51.135Z" }, + { url = "https://files.pythonhosted.org/packages/20/e5/182de15abce8907108a6fdb41487de65beb5099b74dc5841b19b099168db/librt-0.13.0-cp311-cp311-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3dbb2a31882456cadc7053378e81ad7ed7693db4ac9f98ab5f81ef034aa8ec9f", size = 508620, upload-time = "2026-07-08T12:24:52.358Z" }, + { url = "https://files.pythonhosted.org/packages/32/03/33978d32db76e1f66377e8f78e42a2ca3c162143331677d1f50bbad36cfb/librt-0.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c6014e3c80f9c1fe268ef8b0e0ef113bac672cc032f2f93866e7ddad4f3e663d", size = 530363, upload-time = "2026-07-08T12:24:53.503Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f5/b291fbd2d00f7d8287bcbf67b5aa0c6afed4bc26cef23e079629c47a2c04/librt-0.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:091b60a4d2174fc1ec5c34cdc0b72efb6224753d76b7da61ebeab7a191aec8bd", size = 534209, upload-time = "2026-07-08T12:24:55.138Z" }, + { url = "https://files.pythonhosted.org/packages/3e/03/6f41f17939d191bc21609f220da8509316bc62797f078545fe83be522e78/librt-0.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:66cb1138f384a191a6d75f986064841fcfdc0cea98f7bd9c9ab9b38049917588", size = 514254, upload-time = "2026-07-08T12:24:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/af/c2/2e4befa5410a7443019c14abccc94ff619797171f6b72013635fb87f31d7/librt-0.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:17221a7569f8f292aa0014226e48aa25b8c2b08da18088cd230953d0ea0f9cd1", size = 557611, upload-time = "2026-07-08T12:24:57.561Z" }, + { url = "https://files.pythonhosted.org/packages/ab/54/8b69f81448417adbc040a2185f4e2eece1e1994b7dcfaeed4662b30f98a5/librt-0.13.0-cp311-cp311-win32.whl", hash = "sha256:fc67741da44c6eaa90e01eafb586bbba9b51eb5b6ed381ee6f5ae72eb3316d21", size = 104906, upload-time = "2026-07-08T12:24:58.806Z" }, + { url = "https://files.pythonhosted.org/packages/76/5a/f4aaf37b50f2fde12c8c663b83fdd499cdc24f957f19543d7414bfcc9e25/librt-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc99dfb62b23c9207c33d0be8a2e2af7a42e21e6ea388b380a0c948c7b88953b", size = 125852, upload-time = "2026-07-08T12:25:00.065Z" }, + { url = "https://files.pythonhosted.org/packages/f2/99/bf1820e6feeabc2f218c24450ec0c995d6a91e8ba0fd3caf042c9e8adb2a/librt-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:40ccd13c252d3fe473ffc8a57be7565abc8b64cf1b108344c859d5164f7f3e0c", size = 111832, upload-time = "2026-07-08T12:25:01.148Z" }, + { url = "https://files.pythonhosted.org/packages/f0/f4/b2933ddae222dac338476abb872641169a5cfed2c2bb5444a5b07b32b0c3/librt-0.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:30536798f4504c0fad0885b1d371b0539abb081e4570c9d7c641cb51141b49f0", size = 150990, upload-time = "2026-07-08T12:25:02.42Z" }, + { url = "https://files.pythonhosted.org/packages/90/ef/db98f744ca50e6efc9c95c70ee49b77aefac31f6a3fc7c83754a42d6a74f/librt-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:93d24ebb82aa4420b1409c389e7857bc35bd0b668007ac8172427d5c73cc8cc5", size = 155238, upload-time = "2026-07-08T12:25:03.681Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/a197e7bc72baf2c61ce7fdc6906a5054dc05bd8da0819aa894e4857bf87e/librt-0.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb8a1adce42d8b75485a5d56a9623a50bcab995b6079f1dac59fc44034dd93d9", size = 503073, upload-time = "2026-07-08T12:25:05.049Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e7/7887712e27da7c1ab80fcabb1de6eb24243964f6557cae530d4b70706dbd/librt-0.13.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:0763ca2ab66058174f9dee426dc64f5e0a89c24a7df8d3fe3f1836c04e25de4b", size = 496528, upload-time = "2026-07-08T12:25:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/94/f0/f2283385bb6b950b26a1410f4ce51ec27231e0b3a4b925c46366d218b198/librt-0.13.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b222493da6e7b6199db9bd79502436cf5a27da3c1f7fa83c7e285444fc93fd03", size = 531786, upload-time = "2026-07-08T12:25:07.658Z" }, + { url = "https://files.pythonhosted.org/packages/36/11/69ac3b54766ffba5fd7e5acebfb048d66dbe1f9f2d14516c2b3edc59cf87/librt-0.13.0-cp312-cp312-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fadc63331f4388c3dc90090448f682a7e9feafc11481391c1e94f2f907a3976e", size = 524393, upload-time = "2026-07-08T12:25:09.121Z" }, + { url = "https://files.pythonhosted.org/packages/61/5f/d72f95fd444a926a3c14b4e24979474116988dd57a45be242077c45d3c22/librt-0.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:70d9c62a4cffd9f23396cd5ef93fc5d11b31596b9b7d6306074abe3d5fcf09bd", size = 543026, upload-time = "2026-07-08T12:25:10.459Z" }, + { url = "https://files.pythonhosted.org/packages/c4/08/dcd9993ad192737a004ba263d549f8ea605b326b952e7d6205c7d4170b76/librt-0.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66c0e7e6b02a155576df2c77ec933a70b72da726e248c494abf690923e624348", size = 546829, upload-time = "2026-07-08T12:25:11.716Z" }, + { url = "https://files.pythonhosted.org/packages/96/d5/6d9bb2f54e4109a956b7128836529653eb9d740f784bc47ed10a02c1000e/librt-0.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ac04bcd3328eb91d99dfedf6a60d9c1f15d3434e6f6daf922f0420f7d90b85c7", size = 535700, upload-time = "2026-07-08T12:25:13.144Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f2/10946922503858a359492fa27f13e86228bde702116a740ac7b3cd185f24/librt-0.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:db327e7271e653c32040b85ae6188059c924b57d7e1e29f935523fa017cd4e82", size = 573566, upload-time = "2026-07-08T12:25:14.336Z" }, + { url = "https://files.pythonhosted.org/packages/48/a8/94f00e3c99479a18088af3685ea016c42f3c7d5d1964d8dbb40c08d7f1aa/librt-0.13.0-cp312-cp312-win32.whl", hash = "sha256:860bd1d8ba48456ce08feaf8d343a8aaeb2fa086f2bcaa2a923fa3f7a3ff9aa3", size = 106099, upload-time = "2026-07-08T12:25:16.159Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7b/2da9c74c1ed25a89cc4e1c8e007ea2eb4a0f1fafa3e70d757fe3242c5c5c/librt-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:e54a315caf843c8d77e388cadc56ea9ded569935ee2d2347d7ea94992e5aa6fa", size = 126934, upload-time = "2026-07-08T12:25:17.275Z" }, + { url = "https://files.pythonhosted.org/packages/d0/65/aead61bbf3b5358593f9d4779d2a0e88eaf6ec191a6342dde36dd1df6371/librt-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:c718e99a0992127af84385378460db624103b559ab260435abcfe77a4e4ed1c1", size = 112236, upload-time = "2026-07-08T12:25:18.425Z" }, + { url = "https://files.pythonhosted.org/packages/67/3b/18e7b63255297a2bdc9c25c8d6d4ca8eca9f63aceb1252c0f7427ac7099e/librt-0.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a468951af16155824e88bdd8326ebe5bdb371f3ec0ac04642994b98201d914f3", size = 151027, upload-time = "2026-07-08T12:25:19.638Z" }, + { url = "https://files.pythonhosted.org/packages/4d/68/e2248452c00d1a03b45fee1752cdc8f790a476efd2402b75181da88a9e61/librt-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ae01d8512cc17079e53425635327dbf3f7ff57a42c00dec348bf79791c56444c", size = 155152, upload-time = "2026-07-08T12:25:20.851Z" }, + { url = "https://files.pythonhosted.org/packages/0e/16/52b1c99bf19057a062aac39c900cbb81499f6f75d6c537c14463d247ba78/librt-0.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32c26893cd085c1efe83219e78d866da23fb20a066101b8f68210004361d224c", size = 502499, upload-time = "2026-07-08T12:25:22.055Z" }, + { url = "https://files.pythonhosted.org/packages/9f/54/b811151805c795f55e0dedee6ec687b75f9982a8105d240ea3910737a77b/librt-0.13.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:5929da1981a46bcf4b28b1b9499905f0ff58e2419da402a048234e9783acbc4b", size = 496108, upload-time = "2026-07-08T12:25:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/8f/f8/094d6b2bd93f3fdaa54db54cc788c4a365333bddad65ab02e04da0b1d004/librt-0.13.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b85d664d777bab6c0d709416cb42938251fda9e221b79e3a2215d85df5f4f9", size = 531576, upload-time = "2026-07-08T12:25:24.648Z" }, + { url = "https://files.pythonhosted.org/packages/2e/40/541733d5755824f968f7ec39d78ffbd75d145964157ae5e69a09ec6d7326/librt-0.13.0-cp313-cp313-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:531b2df3e9fe96b1fcf73a6d165921e4656be5f58d631d384ebce344298368db", size = 524390, upload-time = "2026-07-08T12:25:25.898Z" }, + { url = "https://files.pythonhosted.org/packages/c6/b5/255673cfdbf5ba663339d36cd863c897289ab4337577e19f9405ce059f36/librt-0.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:109b84a9edf69ad89dc1f66358659e14a031baca95e3e5b0060bd903ede8efd6", size = 543053, upload-time = "2026-07-08T12:25:27.436Z" }, + { url = "https://files.pythonhosted.org/packages/9e/11/ab5005e9c9850710f21e354201bf090646349d3fabf5f951eaf70235729e/librt-0.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1304368a3e7ffc3e9db986796cc5326fdb5943a3567ecc137cff318e4240c0e7", size = 546387, upload-time = "2026-07-08T12:25:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/a2/04/a5d7ce1d1df1afd15ca283dcdf7530ac073e12d69ae8c40879dda96f7868/librt-0.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e4f9b472e7d308d94b62c801982065661158c6ed02790d6c7ddb4337cea0f9c1", size = 535970, upload-time = "2026-07-08T12:25:30.171Z" }, + { url = "https://files.pythonhosted.org/packages/5a/76/927e267a6daa290174ac281b23c9804c8829b042ade9c6f24a065f540958/librt-0.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9f836c37478f167a81200d8c8b2c920a22224564bed2c23d7aeec760965c367a", size = 573582, upload-time = "2026-07-08T12:25:31.507Z" }, + { url = "https://files.pythonhosted.org/packages/10/24/b6c5213efe39c19f9e13605644d0cf063b4ddaa33ac2e45b088e23a70e2e/librt-0.13.0-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:4000d961ff9598ac6ea603c6c836a5ed49bc205ade5fc378b998dfe1e2c36628", size = 82189, upload-time = "2026-07-08T12:25:32.675Z" }, + { url = "https://files.pythonhosted.org/packages/4c/00/d29736be177a906ac0b84a5b04b4fbfa22c776dc2f366de4172b0f968c08/librt-0.13.0-cp313-cp313-win32.whl", hash = "sha256:79e44cff71750d299d61a678e49995b0d5935a9cda238c2574daeca3ba536927", size = 106193, upload-time = "2026-07-08T12:25:33.692Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ac/aff6fb45393cb8912f39dfb156ef6b2d1cadb207ff465fc8f66141054be8/librt-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:54dab44a847d5ad1acd05c8a83fe518ae685516ecf4d3f7cc6e3df2a66767650", size = 126962, upload-time = "2026-07-08T12:25:34.769Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3a/d68cb2b334d53fd30fac81d3a489ce4ba0d9506f4df43fcf676b68352b19/librt-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:d4cb6fbfdf874340ab5e51450753c0f817b6958a3621125ee695bbc3de866566", size = 112127, upload-time = "2026-07-08T12:25:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/7b/66/f49ae0d592bd45b6941e9a8bafcb6a87cddcd501ee7874707e767f01b585/librt-0.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:25218d94b1d2cbc0ba1d8a3f9dc9af578d9646e5ed16443a70cde1dfdcce6d71", size = 149818, upload-time = "2026-07-08T12:25:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/3d/50/51c76d74014d04fb95b6506d286808984b78a2f7a41039094e6b2194ac48/librt-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f26629539d4893c2957a16c41bb058e1e135c1f150f6a2e25ed047f64cf3f5c6", size = 154071, upload-time = "2026-07-08T12:25:39.399Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fe/f19b0f5f82d5a1f2da736586bc840abd00ce07d6388136ae80b7333883fc/librt-0.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4517d47b2b8af26975a406fba7d314de9696d864252e0257c6ea90238cfe27f", size = 494168, upload-time = "2026-07-08T12:25:40.641Z" }, + { url = "https://files.pythonhosted.org/packages/94/bc/b8550c75775127fd31a5f20e8775997f7b527ad661fc8ddccd7497c064f7/librt-0.13.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:f19e181de5b3a1148bb3420b8c4b0b0ea0fce6950099724ad151d6cea5acc180", size = 491054, upload-time = "2026-07-08T12:25:41.905Z" }, + { url = "https://files.pythonhosted.org/packages/30/14/4d0204867623df3f33f86efd3d3692ba5e01321443f4d6eab35a22697618/librt-0.13.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22034924f5b42d5a56371cf271771bfeaabf235a7a8b6264bef2d20013f786c6", size = 523006, upload-time = "2026-07-08T12:25:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/19/0a/c45fc9a260934696bace1ac5df1e148ac92bd71767aee3bf7cd7a4534f4c/librt-0.13.0-cp314-cp314-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c7897db4e95e22468bdda33d8e012ceacd0182abf001e6389d763f0def6286b9", size = 515058, upload-time = "2026-07-08T12:25:44.541Z" }, + { url = "https://files.pythonhosted.org/packages/13/0a/50c5ce45b326854ef8fa6ae4c36cf5142e5c55315eaf9e51d0ae73ac4da3/librt-0.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1ce61b3746545029d4f5c17d6bd74b676254ad98433086c846ffb5e8fa73f007", size = 534025, upload-time = "2026-07-08T12:25:45.825Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/08c413c8f93fc13b8103624fce38e5caa86cd08cbbc8465870ab287af54b/librt-0.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:46c330e82565962c761dbce7941be2cff7db674ee807455a8d0cadc5f9b759b0", size = 540557, upload-time = "2026-07-08T12:25:47.059Z" }, + { url = "https://files.pythonhosted.org/packages/b3/c1/93af71fb4a364952210051811dd4e40174e79656b050c89cacac18af3330/librt-0.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:375f5af8f99cbaa99dd293af986e3d57caabc9ba81a5d3f021603764854197a1", size = 523201, upload-time = "2026-07-08T12:25:48.392Z" }, + { url = "https://files.pythonhosted.org/packages/c1/6e/9766f07b676a4889d9f8bc2864e9ba5fff165653143ef4dda7df6aa34d16/librt-0.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9320d34c3376ae204b2cd176e8d4883a013934e0aef822f1aed9c536490c275d", size = 565740, upload-time = "2026-07-08T12:25:49.678Z" }, + { url = "https://files.pythonhosted.org/packages/a2/1e/664e3472ce2b6e10e9b83f29d4a36eb982ff6b5a169ae7567bba3a4c4ff5/librt-0.13.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:9af313c66157a69dc69ea0059a66961692250e0dc95af9c385a48ffb770a0d16", size = 81611, upload-time = "2026-07-08T12:25:50.857Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d4/8582a4d65e2234673685e07309d02c230b28a85724eb0acbf13f019b7f6e/librt-0.13.0-cp314-cp314-win32.whl", hash = "sha256:f2a7253458e34f33543551394ae4fe104b497ec2a65ac266074de64c1df82e37", size = 100106, upload-time = "2026-07-08T12:25:52.03Z" }, + { url = "https://files.pythonhosted.org/packages/63/ce/0cb99efe6086b46cd985dc26672166fae312a239690e75871f7fafbd3fc5/librt-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:a3dfe4edf10e8ed7e55b026a8bfc2c2a8704218b659cd4bffdf604fab966dc39", size = 121209, upload-time = "2026-07-08T12:25:53.166Z" }, + { url = "https://files.pythonhosted.org/packages/26/85/4f3ccb083a3c9b0d42e223acdb3c3f507953324a59cdcab4826e8e2e3b89/librt-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:68a5faee4bba381cb93b5961f684a514cf0053cb92308ff9c792c2fea0b174c6", size = 106404, upload-time = "2026-07-08T12:25:54.253Z" }, + { url = "https://files.pythonhosted.org/packages/b2/77/333191499538c8e8189de7a4cba8e6f49ee949fd6d6e6324b21fd1522466/librt-0.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a38fb81d8376dfa2f8963b265fec07637802b0d01e2a127c19c66cb070fb24f5", size = 159231, upload-time = "2026-07-08T12:25:55.432Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9e/2aa83758f22c278b837a1d8025898434ce2b8bff36678d5330ecaef56dff/librt-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d4c8d9bd5abce34b2e75edb3bf37ab0f34e49b1f915a40ae8468eb7c85bc5b46", size = 161300, upload-time = "2026-07-08T12:25:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c0/86791e936553ca763d6b3c2fb4d31d596cd00e14fa631c283a40ba01559a/librt-0.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:387e2f1d27e89bffe0d3f520f0da0662c973fd607ca16c1808f8a5085419485e", size = 582056, upload-time = "2026-07-08T12:25:58.144Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d3/a9ec15984a185e000c4d2a16ba28bd623124ad4c38a10974c7ff78e3a893/librt-0.13.0-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:4f6db193d2e5e0ed60359b9a5a682cd67205d0d3b1e459a867dd4b5c4e7eaa7a", size = 562758, upload-time = "2026-07-08T12:25:59.544Z" }, + { url = "https://files.pythonhosted.org/packages/3c/af/dbe36b78b19c06a55097f99305e4ea9458e2273e6ae16a3cbecaad7ee978/librt-0.13.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d38604854e8d22faadf683ec6c02bb0f886e2ba56ef981a1c36ee275f21ea22", size = 602095, upload-time = "2026-07-08T12:26:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/2a/a8/2966891b4dd2830f5203fbee92ac2c4947653a2390ba73dfa44244fad025/librt-0.13.0-cp314-cp314t-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:371f7ce73026815dafd51c50ce38416e91428b28c4b2ec97cd39271164b0045c", size = 593452, upload-time = "2026-07-08T12:26:02.352Z" }, + { url = "https://files.pythonhosted.org/packages/61/f5/4df8bfc8405ecf8c0d525b4d69636f694bdd8620b313ec8b76e54a5926cc/librt-0.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3aaedf52171bee90860704c560bc798fe83b76247df47568e0197e9b13c735a0", size = 623729, upload-time = "2026-07-08T12:26:04.294Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/9ac202dffc8db06f75d06c08c2f9f6ff054be67d21272dcc078fa1cc0c57/librt-0.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:96bad8725a4f196a798366c25ce075d1f7543a4ec045ffc13e6a7ec095cdab04", size = 617077, upload-time = "2026-07-08T12:26:05.845Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f0/ebe38610716aee5cb28efd95089bb90192096179802779381e1c5dcf239c/librt-0.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:6bf6a559ffe4a93bbea6cf31ddf01a7fd9ba342ef51f27beb178e318b74acd61", size = 599561, upload-time = "2026-07-08T12:26:07.21Z" }, + { url = "https://files.pythonhosted.org/packages/4f/5c/c2e72e236fff7abc716d5b1753b8b8cd3ea85ac46fe17d2e7c51d4e1c723/librt-0.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:301067672387902c55f94b51d5022304b36c966ea9fe1f21caab99a9bef487c9", size = 645511, upload-time = "2026-07-08T12:26:08.562Z" }, + { url = "https://files.pythonhosted.org/packages/0c/99/6203ce619dee940d6bfbe099ec3fe4be00a68e9d60f70abf906cf124fe66/librt-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:5fdcf34f86de8fb66d7dc7589f96ba91c4aa46671200d400e6fd6f109a483f18", size = 104357, upload-time = "2026-07-08T12:26:09.828Z" }, + { url = "https://files.pythonhosted.org/packages/52/dd/843b6314087c41657c7036d7914d8f294bdf9b580aa8513ea0588c8e9a3d/librt-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:260c33e92263fa629b4f6d3c51967a1c2158fe6c33237aaa3ebeac586b085259", size = 126998, upload-time = "2026-07-08T12:26:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/3dcec2884ba1b0806d1408612555c38dd5d68e90156b59f75f6e36435c3a/librt-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2f281549a4c52ac7bb97997f14353f8bd0e53a34ca0dad1c905cfd0b4a58ae99", size = 110771, upload-time = "2026-07-08T12:26:12.303Z" }, + { url = "https://files.pythonhosted.org/packages/07/7f/70d7bb462049d5393bdd9c34cca84d3e69c8e42d8aefe317909081e956a6/librt-0.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f442e3954b1addc759faae22a7c9a3f1e16d7d1db3f484279dc27d62e06968fa", size = 148802, upload-time = "2026-07-08T12:26:13.421Z" }, + { url = "https://files.pythonhosted.org/packages/bc/dd/81ebb227575da36f1321d83ebd8248882a8b580518f5aeb64ecc3ad5b0f0/librt-0.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e786428f291dd2d2f1cbfc0e0caa45a2e395fab0ad3e2c9314daa8873414390", size = 154436, upload-time = "2026-07-08T12:26:14.608Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8e/e048589ce83caa796dd1e801f66b1a69e4b27f8779326f84c5456176a28b/librt-0.13.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21b7ac084f701a9cdff6139745a6620579d65a9379ac2d9d50a86368b109e63c", size = 493651, upload-time = "2026-07-08T12:26:15.794Z" }, + { url = "https://files.pythonhosted.org/packages/ba/a7/917e7ec7556fb13c78702f933bf47709303f5b741aed99c107b5051c1c0e/librt-0.13.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.manylinux_2_28_i686.whl", hash = "sha256:a6e556d6aba31c93dd97ce661d66614d2429c0a3923f9dc8f0af7e8df10223a4", size = 484354, upload-time = "2026-07-08T12:26:17.275Z" }, + { url = "https://files.pythonhosted.org/packages/ee/6a/4505cadde67b794ac60c328012f2517df6babc115ea388953b0c2a4996ca/librt-0.13.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3657346f867469e962549435aa05fd15330b1d6a92829f8e27988e194382d005", size = 515032, upload-time = "2026-07-08T12:26:18.639Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d0/fc39a7df0e6cbf6f48cd446a2a7ffc033601f7ee96d6d334abda1a0359f4/librt-0.13.0-cp39-cp39-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:791aa18a373b90da8ac3c44fc77544f33fdf53ae403acdce9b39f1c26b4a3b94", size = 508998, upload-time = "2026-07-08T12:26:19.978Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/203a7c6c6bb8911fac67bc6ba2e239d71f9cf8a0ad37a554a3c1294d7a08/librt-0.13.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d6fb0eaa108814581c4d3bfbd068c3fb6757812a81415008d1bae08267cca360", size = 531801, upload-time = "2026-07-08T12:26:21.486Z" }, + { url = "https://files.pythonhosted.org/packages/aa/12/c153ebcab09d9cb020ea12b59c9e36cc50aa39920f1f91f6a1b4a3361be4/librt-0.13.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a001519c315d5db40710f2665d32c4791f1d4779fc96a9423fd18d92c8b9ac7b", size = 536830, upload-time = "2026-07-08T12:26:23.069Z" }, + { url = "https://files.pythonhosted.org/packages/b1/93/df8b520d01f2889c9a0c9584b39409d7fbb818f0e36017d18bd6835fb8c4/librt-0.13.0-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:d9188caac26e47671b52836a5e2a49873a7fc11c673b0c122d22515f98bc14e1", size = 516579, upload-time = "2026-07-08T12:26:24.393Z" }, + { url = "https://files.pythonhosted.org/packages/46/09/090ad042150a944a015bc1cfde088e26ed69612f866f43d3876351b6cb87/librt-0.13.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:05d96b80b95d3a2721b619f8982b8558848b04875bb4772fd54842b59f61dd97", size = 558495, upload-time = "2026-07-08T12:26:25.73Z" }, + { url = "https://files.pythonhosted.org/packages/2d/9e/c63f5e8021e3b3d7367ee91c1eafa7cabb455e937cdf7eb6cf4c453db92d/librt-0.13.0-cp39-cp39-win32.whl", hash = "sha256:c3cd253cf32fe4f4662960d6bf7d55cb8be0c31a5d644a4d48aeafebaff3409a", size = 104913, upload-time = "2026-07-08T12:26:27.004Z" }, + { url = "https://files.pythonhosted.org/packages/c7/43/193e0190a0752c8d621ef3eb6482455e410d2d3dfc63c1da000b39608e5d/librt-0.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:b15e26cc0fe622d0c67e98bee6ef6bc8f792e20ee3006aa12627a00463d9399f", size = 125583, upload-time = "2026-07-08T12:26:28.593Z" }, +] + +[[package]] +name = "markdown" +version = "3.9" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "importlib-metadata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, +] + +[[package]] +name = "markdown" +version = "3.10.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/ff/7841249c247aa650a76b9ee4bbaeae59370dc8bfd2f6c01f3630c35eb134/markdown_it_py-4.2.0.tar.gz", hash = "sha256:04a21681d6fbb623de53f6f364d352309d4094dd4194040a10fd51833e418d49", size = 82454, upload-time = "2026-05-07T12:08:28.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/81/4da04ced5a082363ecfa159c010d200ecbd959ae410c10c0264a38cac0f5/markdown_it_py-4.2.0-py3-none-any.whl", hash = "sha256:9f7ebbcd14fe59494226453aed97c1070d83f8d24b6fc3a3bcf9a38092641c4a", size = 91687, upload-time = "2026-05-07T12:08:27.182Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, + { url = "https://files.pythonhosted.org/packages/56/23/0d8c13a44bde9154821586520840643467aee574d8ce79a17da539ee7fed/markupsafe-3.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", size = 11623, upload-time = "2025-09-27T18:37:29.296Z" }, + { url = "https://files.pythonhosted.org/packages/fd/23/07a2cb9a8045d5f3f0890a8c3bc0859d7a47bfd9a560b563899bec7b72ed/markupsafe-3.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", size = 12049, upload-time = "2025-09-27T18:37:30.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e4/6be85eb81503f8e11b61c0b6369b6e077dcf0a74adbd9ebf6b349937b4e9/markupsafe-3.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", size = 21923, upload-time = "2025-09-27T18:37:31.177Z" }, + { url = "https://files.pythonhosted.org/packages/6f/bc/4dc914ead3fe6ddaef035341fee0fc956949bbd27335b611829292b89ee2/markupsafe-3.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", size = 20543, upload-time = "2025-09-27T18:37:32.168Z" }, + { url = "https://files.pythonhosted.org/packages/89/6e/5fe81fbcfba4aef4093d5f856e5c774ec2057946052d18d168219b7bd9f9/markupsafe-3.0.3-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", size = 20585, upload-time = "2025-09-27T18:37:33.166Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f6/e0e5a3d3ae9c4020f696cd055f940ef86b64fe88de26f3a0308b9d3d048c/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", size = 21387, upload-time = "2025-09-27T18:37:34.185Z" }, + { url = "https://files.pythonhosted.org/packages/c8/25/651753ef4dea08ea790f4fbb65146a9a44a014986996ca40102e237aa49a/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", size = 20133, upload-time = "2025-09-27T18:37:35.138Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0a/c3cf2b4fef5f0426e8a6d7fce3cb966a17817c568ce59d76b92a233fdbec/markupsafe-3.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", size = 20588, upload-time = "2025-09-27T18:37:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1b/a7782984844bd519ad4ffdbebbba2671ec5d0ebbeac34736c15fb86399e8/markupsafe-3.0.3-cp39-cp39-win32.whl", hash = "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", size = 14566, upload-time = "2025-09-27T18:37:37.09Z" }, + { url = "https://files.pythonhosted.org/packages/18/1f/8d9c20e1c9440e215a44be5ab64359e207fcb4f675543f1cf9a2a7f648d0/markupsafe-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", size = 15053, upload-time = "2025-09-27T18:37:38.054Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d3/fe08482b5cd995033556d45041a4f4e76e7f0521112a9c9991d40d39825f/markupsafe-3.0.3-cp39-cp39-win_arm64.whl", hash = "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", size = 13928, upload-time = "2025-09-27T18:37:39.037Z" }, +] + +[[package]] +name = "matplotlib-inline" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bd/c0/9f7c9a46090390368a4d7bcb76bb87a4a36c421e4c0792cdb53486ffac7a/matplotlib_inline-0.2.2.tar.gz", hash = "sha256:72f3fe8fce36b70d4a5b612f899090cd0401deddc4ea90e1572b9f4bfb058c79", size = 8150, upload-time = "2026-05-08T17:33:33.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/09/5b161152e2d90f7b87f781c2e1267494aef9c32498df793f73ad0a0a494a/matplotlib_inline-0.2.2-py3-none-any.whl", hash = "sha256:3c821cf1c209f59fb2d2d64abbf5b23b67bcb2210d663f9918dd851c6da1fcf6", size = 9534, upload-time = "2026-05-08T17:33:32.055Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mergedeep" +version = "1.3.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, +] + +[[package]] +name = "mkdocs" +version = "1.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "click", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "ghp-import" }, + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "jinja2" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "markupsafe" }, + { name = "mergedeep" }, + { name = "mkdocs-get-deps" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "pyyaml" }, + { name = "pyyaml-env-tag" }, + { name = "watchdog" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, +] + +[[package]] +name = "mkdocs-get-deps" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata", marker = "python_full_version < '3.10'" }, + { name = "mergedeep" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, +] + +[[package]] +name = "mkdocs-material" +version = "9.7.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "backrefs", version = "6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "backrefs", version = "7.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "colorama" }, + { name = "jinja2" }, + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "mkdocs" }, + { name = "mkdocs-material-extensions" }, + { name = "paginate" }, + { name = "pygments" }, + { name = "pymdown-extensions", version = "10.21.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pymdown-extensions", version = "11.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "requests", version = "2.32.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "requests", version = "2.34.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/cd/c05d3a530ba7934f144fb45f7203cd236adc25c7bdcc34673d202f4b0278/mkdocs_material-9.7.7.tar.gz", hash = "sha256:c0649c065b1b0512d60aad8c10f947f8e455284475239b364b610f2deb4d0855", size = 4097923, upload-time = "2026-07-17T16:21:33.156Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/21/17c1bc9e6f47c972ad66fb2ac2568f99f90f1207eeb6fc3b34d094dba7b5/mkdocs_material-9.7.7-py3-none-any.whl", hash = "sha256:8ea9bb1737a5b524a5f9dcf2e1b4ebda8274ae3008aa7845720a97083bef708f", size = 9305438, upload-time = "2026-07-17T16:21:30.017Z" }, +] + +[[package]] +name = "mkdocs-material-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, +] + +[[package]] +name = "mypy" +version = "1.19.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/63/e499890d8e39b1ff2df4c0c6ce5d371b6844ee22b8250687a99fd2f657a8/mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec", size = 13101333, upload-time = "2025-12-15T05:03:03.28Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/095626fc136fba96effc4fd4a82b41d688ab92124f8c4f7564bffe5cf1b0/mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b", size = 12164102, upload-time = "2025-12-15T05:02:33.611Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/952928dd081bf88a83a5ccd49aaecfcd18fd0d2710c7ff07b8fb6f7032b9/mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6", size = 12765799, upload-time = "2025-12-15T05:03:28.44Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/93c2e4a287f74ef11a66fb6d49c7a9f05e47b0a4399040e6719b57f500d2/mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74", size = 13522149, upload-time = "2025-12-15T05:02:36.011Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/33a294b56aaad2b338d203e3a1d8b453637ac36cb278b45005e0901cf148/mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1", size = 13810105, upload-time = "2025-12-15T05:02:40.327Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/3e82603a0cb66b67c5e7abababce6bf1a929ddf67bf445e652684af5c5a0/mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac", size = 10057200, upload-time = "2025-12-15T05:02:51.012Z" }, + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/88436084550ca9af5e610fa45286be04c3b63374df3e021c762fe8c4369f/mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3", size = 13102606, upload-time = "2025-12-15T05:02:46.833Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a5/43dfad311a734b48a752790571fd9e12d61893849a01bff346a54011957f/mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a", size = 12164496, upload-time = "2025-12-15T05:03:41.947Z" }, + { url = "https://files.pythonhosted.org/packages/88/f0/efbfa391395cce2f2771f937e0620cfd185ec88f2b9cd88711028a768e96/mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67", size = 12772068, upload-time = "2025-12-15T05:02:53.689Z" }, + { url = "https://files.pythonhosted.org/packages/25/05/58b3ba28f5aed10479e899a12d2120d582ba9fa6288851b20bf1c32cbb4f/mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e", size = 13520385, upload-time = "2025-12-15T05:02:38.328Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a0/c006ccaff50b31e542ae69b92fe7e2f55d99fba3a55e01067dd564325f85/mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376", size = 13796221, upload-time = "2025-12-15T05:03:22.147Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ff/8bdb051cd710f01b880472241bd36b3f817a8e1c5d5540d0b761675b6de2/mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24", size = 10055456, upload-time = "2025-12-15T05:03:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, +] + +[[package]] +name = "mypy" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "ast-serialize" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, + { name = "mypy-extensions" }, + { name = "pathspec" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/af/4e516a05d3ca2eb9283e9ec45b2c02225c1514dd6da49fd3c9eaa6639370/mypy-2.3.0.tar.gz", hash = "sha256:465965d41cd9a2726694e983e8ce7113259327bec798115d1e1dfa2a52fb666e", size = 3988104, upload-time = "2026-07-13T11:34:53.387Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/09/f2f5f45dae0c9a0891e4751a73312730e009395102e5d72a22a976cca41f/mypy-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fa8d916ac3b705af733c4c1e6c9ebe38fd0d52beb15b105c3e8355b55e6ecdc", size = 14927774, upload-time = "2026-07-13T11:28:38.224Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/345367effd3a6877275a94d481614bfca983f45e028c6290e2cc54603811/mypy-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28e1e2af8cd8fff551fd30f2fe4b03fb76764ac8b1ba6c6a1bd00ad32b412db3", size = 14000127, upload-time = "2026-07-13T11:30:19.57Z" }, + { url = "https://files.pythonhosted.org/packages/99/6c/a10b7a7b9f0a755fb94e27ae834d4cea9ad6c5221f9325eef8f182641feb/mypy-2.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3e77244df3843048c3f927182916730e40c124cbaa43905c1fb86cb382aa0805", size = 14229437, upload-time = "2026-07-13T11:28:17.765Z" }, + { url = "https://files.pythonhosted.org/packages/d9/bd/a26a602acb1bbf849fa4bdac4bc657ee2f11c0c2a764a2cc87a5304e865c/mypy-2.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9559ab18a9c9957dfa3004ab57cd4bac5f26a724329a9584e583367f0c2e1117", size = 15171457, upload-time = "2026-07-13T11:29:01.834Z" }, + { url = "https://files.pythonhosted.org/packages/7f/14/124f462bef69bcbc90b9358088460b6091954a3e004852fcd9948db617a5/mypy-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:09abd66d8685e73f8f7d17b847c3e104d9a7b164a8706ea87d6c96a3d45816d5", size = 15478281, upload-time = "2026-07-13T11:32:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/db/a4/8bdca6a8ac8d856d82ed049144af2721245a135c2e8001d3890c93975852/mypy-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e91adad1ca81742ac7ef9893959911df867752206b37135185e88dfb3c89494", size = 11148008, upload-time = "2026-07-13T11:34:17.332Z" }, + { url = "https://files.pythonhosted.org/packages/83/41/490eea348e60ba50decec20bc750605444149a5d7a8cc560042f90ba2c75/mypy-2.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:6f99ec626e3c3a2f7c0b22c5b90ddb5dabb1c18729c971e9bdaca1f1766d2cee", size = 10142329, upload-time = "2026-07-13T11:32:52.116Z" }, + { url = "https://files.pythonhosted.org/packages/e6/b9/d75b3082b05f1b3028828aeb18e74ae5ab0a0936051bbf1f32f59f654747/mypy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3419d00717afbc5265b50dd14b1278f29ea4884dd398ab67873489ac093fd329", size = 14838725, upload-time = "2026-07-13T11:32:44.655Z" }, + { url = "https://files.pythonhosted.org/packages/a9/50/79a65c6ea6e115bc73296038a4543b2d5c91f07912b918a2c616a2514bba/mypy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cfca8ee88544090f86b6dcce05ec55d66eb48a762412ac2507810ba4bd793b6f", size = 13911128, upload-time = "2026-07-13T11:32:02.021Z" }, + { url = "https://files.pythonhosted.org/packages/90/48/e11ed7716c26953ca321f726e452e374dbf81a6f2b8b212ec02af29b6b8f/mypy-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75cbb4b9ef04a0c84a957f07abc4504fbf64b8dcc145675101f2d3a78a4b1d6a", size = 14146742, upload-time = "2026-07-13T11:33:03.313Z" }, + { url = "https://files.pythonhosted.org/packages/06/72/6807565b1c4861ef66f7fdd98b51c61556356eab80235717b46c53bb8627/mypy-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:982e3d53dd23d0a4cef67dd66791fdbede0cf38f9eb617bf47663554c51e1e36", size = 15081418, upload-time = "2026-07-13T11:31:13.899Z" }, + { url = "https://files.pythonhosted.org/packages/00/80/1ea14c5d80e589e415973db3e47c78c2219a305b808b2b506395342c1d79/mypy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85c5385b93012ffa3b31479ab579aef5415f4f3a32c6cf1ae07a984d2a0ff461", size = 15328164, upload-time = "2026-07-13T11:31:35.723Z" }, + { url = "https://files.pythonhosted.org/packages/37/28/8223157404a3d51920078459c37f80fbdc590e1d8ea049dc5ce48643022a/mypy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:13b1b16e2fa39f3b2e33fb1c468abc7a69369fa2e886b4b87b5afc81472325cd", size = 11136472, upload-time = "2026-07-13T11:27:37.018Z" }, + { url = "https://files.pythonhosted.org/packages/6f/cc/ea27e5959c5f258585a756b252031f3b313583d81b5064b2bebc41d3706b/mypy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5cd2f027a972a4a5f2278a11fac9747f5f81a53a30b714d74950b6807e55568", size = 10135800, upload-time = "2026-07-13T11:30:08.92Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/0e7e592619e2133596a47cdd642534b0456545c218430bd3b9d8fefdd1b1/mypy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d53fc67b9d28a43c6199077f49fea0f05839e36cf6158500331c9549225e5a5", size = 15026523, upload-time = "2026-07-13T11:34:49.206Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/1e1731df090a857df2807177a4626863e5ac0f0256513c35780efe53986f/mypy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbc00cee7bdbb9291979ddc9d08034a29dfcda4932628c9bbc28c1edd589df0c", size = 14032189, upload-time = "2026-07-13T11:33:57.168Z" }, + { url = "https://files.pythonhosted.org/packages/44/95/cab921f4a806e171f34113e6181dd23c55358ccf6a80741269ef594a410e/mypy-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:04e617030eca5221909c8b7d8d7fd1c637948199aa2100b2ad9813feb07e1491", size = 14198696, upload-time = "2026-07-13T11:32:12.767Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/e6d008bb19fe446e3662d85e0e2717bf9f2d611a2164fb29d6e067dbf46c/mypy-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56c184d2c20ca6b6378d58d1960270a767f41f5e44acbbd27f05effef4f4e1d7", size = 15286904, upload-time = "2026-07-13T11:34:27.594Z" }, + { url = "https://files.pythonhosted.org/packages/db/83/94397c9293608a364aa03e8084fb34ede4ae976a260384b9b52929308135/mypy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3961a4a34b05f7c74b0f05aa51fbfe99a2d1e126038df40318d15c8f558b7ef3", size = 15528342, upload-time = "2026-07-13T11:34:07.819Z" }, + { url = "https://files.pythonhosted.org/packages/cf/96/d8b37d819adec6cfccfb1fd3afc1735d94717ddeafb45536db9c6943e09b/mypy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:b1942b9314d4c784b8ea1dbab4972603290e5dd5630f06675f13aec97526bc4c", size = 11218346, upload-time = "2026-07-13T11:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/2b/cd/cd9f725b19b19e5b530a154cf9bcf9e94279c5d55b3c34fb42b3aa48ea1b/mypy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:be51653d7669d7d7955d613b8d0bb57d5b652eaf71a873ddf65ac87254dd2595", size = 10204525, upload-time = "2026-07-13T11:31:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/f7d056eb0294586a572d0d0d89580ec633c064db520f11d37d5a2fb833bd/mypy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:91ad22a52ae2c7e621c2f67c94d5a17f66b3209a4cff5cf8a573579835c69e97", size = 14947298, upload-time = "2026-07-13T11:27:47.734Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/db3e7af01e7844d21662c6ddc1f7825ec7cb4053f0391ac02faf3638396f/mypy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:99ac767cc5d3b64c8d0ae226ead10c96694f94e4e7da1668642225dcd4e75aac", size = 13950768, upload-time = "2026-07-13T11:27:57.726Z" }, + { url = "https://files.pythonhosted.org/packages/d9/fb/43c031f0190513d1ec248ed037eceb742ddd2a4d74bbf406658a28173837/mypy-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6d2c484742a4d7b0ed6d07b143375624d3b899c5749c7b3c947f56261f48a6", size = 14151586, upload-time = "2026-07-13T11:29:18.615Z" }, + { url = "https://files.pythonhosted.org/packages/ec/c3/f8b2ffc60883084da91be51af58e88a7ffd4ff9795acb7d902ff88d31eb1/mypy-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7da939dd335cfd2ad788bdfd081c9f4e47634ab995e5a45eb15fd1e5bc052f8b", size = 15227411, upload-time = "2026-07-13T11:30:29.904Z" }, + { url = "https://files.pythonhosted.org/packages/83/2e/16b917fc7adcf03f1aadddfc93aab804ffb234b1ab09c0ffd6d92a5d34a2/mypy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7247eb2824f996722a949530183394921ca71deb9680052a338cf53cff7925c2", size = 15478790, upload-time = "2026-07-13T11:33:14.686Z" }, + { url = "https://files.pythonhosted.org/packages/c0/88/aaa65a93c73d0cdae7e42f8adb302bf6885bb281302084f99d0290a35347/mypy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:75b0984bb3cbd76bb5c9291a8671f7ae66ca3b51c7584c358fc2e923259f0757", size = 11234919, upload-time = "2026-07-13T11:33:39.28Z" }, + { url = "https://files.pythonhosted.org/packages/35/19/b40de63f1a80e63bc2d40f0679a6a8dbd34e95176c8122119bdf406aa552/mypy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:d78fcf900b59cb7e82cb7e3a235e31b462d9333d92285bd1e4952d355b8ffba1", size = 10201510, upload-time = "2026-07-13T11:31:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/a4/58/fa0ae047da911f540284009b4f44b96fe09d83c076d7c103e9d645f46303/mypy-2.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ea317b060ce83e26050f8f9e4d7d6bf44ed7597c8ff9990bccffbb9d1d8522db", size = 14941909, upload-time = "2026-07-13T11:32:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/15/14/2ba1d61452d7c2a7fe12741e8d374e52b183476b07aa7f9e2a0d02b0720a/mypy-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:094af99f92638aa92852326188b85a89e50f4a472f44827c03362228482f0762", size = 13967581, upload-time = "2026-07-13T11:30:00.587Z" }, + { url = "https://files.pythonhosted.org/packages/ed/5a/483fb9e5ffbbb1a28dccc7b0a13d141b17ac769b6c9f488c0a0c63698962/mypy-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de121747278144fc9ae7caa2e978cf5df12aebc82933182f5b3b86081a30baef", size = 14168807, upload-time = "2026-07-13T11:28:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/ae/77/70d7a10732063beb74ad713682cf871e88f5c5fa39bfc8beff8a524bf9cb/mypy-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37fa4de896a84e2dc9200d91e614c22563b43d1a266789d4bbac7b22ebe6192b", size = 15200144, upload-time = "2026-07-13T11:31:25.283Z" }, + { url = "https://files.pythonhosted.org/packages/56/72/766218ac783be4fdfcd699b90037b63017348a3e86fb2c1fbfb18302637d/mypy-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f1b3a98dfd21058bc759bb3337d5d1f61d0fdf9f3cf9c00f4291790fb5427bff", size = 15460389, upload-time = "2026-07-13T11:29:29.077Z" }, + { url = "https://files.pythonhosted.org/packages/38/4e/8a9db7411ecb8ec0cb1fd05dba432f28bafffcd38b4e887714a4a0506689/mypy-2.3.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:944c665d984157cb96a679dfb7a4a81dd1d36b24b9c284b699514e6e626b82d4", size = 7753664, upload-time = "2026-07-13T11:29:08.147Z" }, + { url = "https://files.pythonhosted.org/packages/65/4c/c3f8bfd6ed0e5e38b5a244403b27f821d433443df5a15a278417c10a3a3c/mypy-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:4359424140d985192c778c1ce2c114a10c1ca58a381ed79cfa70d37df94b299f", size = 11417237, upload-time = "2026-07-13T11:33:47.467Z" }, + { url = "https://files.pythonhosted.org/packages/3c/00/89a32eaf5ccf174bc4f90db0eaea5d70636c01b8d49f384bdab2e8834390/mypy-2.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:3dd0bed92c4bdec57c42505b96416fb9e6a5aa7be84d2809bcd5f2ecec2860d7", size = 10389252, upload-time = "2026-07-13T11:31:43.81Z" }, + { url = "https://files.pythonhosted.org/packages/31/56/104f93d69aa9f339b6b9d3b0a7faa699b8b466c942cf3ae86cc2a2ec0915/mypy-2.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:691fdc37132b1ae628d834f672e74de83462d9fb4aff621835767fb43a8dd373", size = 16385495, upload-time = "2026-07-13T11:29:49.818Z" }, + { url = "https://files.pythonhosted.org/packages/d2/03/f1d2123313f55efafdd27706960f43a771c62f1b68426c76043f3ab9ebf3/mypy-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:aec15d465d477558fd842757b487849007311cf3897849cdda0e3162ac0ac556", size = 15098155, upload-time = "2026-07-13T11:30:40.301Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5d/d5f9200399b445e81726c4f23becee33f233aee81c72680b1ef3a258b641/mypy-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b352b7e49f5e6576009e8df730e1ff4f915cb565b851b396d2ffe2f5a6f5da88", size = 15514155, upload-time = "2026-07-13T11:34:38.569Z" }, + { url = "https://files.pythonhosted.org/packages/cd/ce/69977c555f08faa3190cfde44189b89dbd56861b1ab97aa18fc5f3a2e4a3/mypy-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c6c6bf687b17f90dbfcad95b960d32eaa0154c00da45f03ab50bf8952e047fe", size = 16766351, upload-time = "2026-07-13T11:33:29.195Z" }, + { url = "https://files.pythonhosted.org/packages/bc/92/6648b6caa3ab9e00f9ac0c2a78307805f873dd48139b24a6f6f7c3667bbf/mypy-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f4ed18f111bfe2d599bca7468e7f9251042c1c2118f762c8de2766a56d773c60", size = 17043490, upload-time = "2026-07-13T11:30:53.927Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ab/0dc91d80f3f016634c68d451f294a97320fe903a9b6f90b9e57b3f7f1717/mypy-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0b025a93cffb9781d231f232be07a17912f35f10a313c24f301c81e842870654", size = 12146869, upload-time = "2026-07-13T11:29:38.874Z" }, + { url = "https://files.pythonhosted.org/packages/85/b5/4c964d02634ba81f4d1c84838e5c5b18ab06d13ed568960f5d6318495ccc/mypy-2.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:adebc76aab4f3495a88b41d48aa4aff0c03f2822501da76625afcca5975f19e5", size = 10965113, upload-time = "2026-07-13T11:28:07.056Z" }, + { url = "https://files.pythonhosted.org/packages/2c/fa/fdc54fe583ba3cafbcedfb70eeeaf03849f75b1827a07096c7bd996f582d/mypy-2.3.0-py3-none-any.whl", hash = "sha256:6b1cdb579446b60432432b2b2403a6201b4b475a004d7f488511c9ba177c9e88", size = 2753292, upload-time = "2026-07-13T11:33:18.48Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + +[[package]] +name = "packaging" +version = "26.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/f1/e7a6dd94a8d4a5626c03e4e99c87f241ba9e350cd9e6d75123f992427270/packaging-26.2.tar.gz", hash = "sha256:ff452ff5a3e828ce110190feff1178bb1f2ea2281fa2075aadb987c2fb221661", size = 228134, upload-time = "2026-04-24T20:15:23.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b2/87e62e8c3e2f4b32e5fe99e0b86d576da1312593b39f47d8ceef365e95ed/packaging-26.2-py3-none-any.whl", hash = "sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e", size = 100195, upload-time = "2026-04-24T20:15:22.081Z" }, +] + +[[package]] +name = "paginate" +version = "0.5.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, +] + +[[package]] +name = "parso" +version = "0.8.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/4b/90c937815137d43ce71ba043cd3566221e9df6b9c805f24b5d138c9d40a7/parso-0.8.7.tar.gz", hash = "sha256:eaaac4c9fdd5e9e8852dc778d2d7405897ec510f2a298071453e5e3a07914bb1", size = 401824, upload-time = "2026-05-01T23:13:02.138Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5d/8268b644392ee874ee82a635cd0df1773de230bde356c38de28e298392cc/parso-0.8.7-py2.py3-none-any.whl", hash = "sha256:a8926eb2a1b915486941fdbd31e86a4baf88fe8c210f25f2f35ecec5b574ca1c", size = 107025, upload-time = "2026-05-01T23:12:58.867Z" }, +] + +[[package]] +name = "pathspec" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" }, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ptyprocess" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523", size = 63772, upload-time = "2023-11-25T06:56:14.81Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.4.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/78/9b/560e4be8e26f6fd133a03630a8df0c663b9e8d61b4ade152b72005aec83b/platformdirs-4.11.0.tar.gz", hash = "sha256:0555d18370482847566ffabcaa53ad7c6c1c29f195989ae1ed634a05f76ea1e0", size = 31953, upload-time = "2026-07-21T13:09:36.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/68/d8d58938dfb1370b266a1a729e6d77a985be23689a0496498ee17b2cbf90/platformdirs-4.11.0-py3-none-any.whl", hash = "sha256:360ccded2b7fce0af0ff80cc8f5942a1c5d99b0e856033acb030bfc634709e74", size = 23247, upload-time = "2026-07-21T13:09:35.422Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "cfgv", version = "3.4.0", source = { registry = "https://pypi.org/simple" } }, + { name = "identify", version = "2.6.15", source = { registry = "https://pypi.org/simple" } }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "cfgv", version = "3.5.0", source = { registry = "https://pypi.org/simple" } }, + { name = "identify", version = "2.6.19", source = { registry = "https://pypi.org/simple" } }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/3a/ddb78f32a0814e66b18a099377a106a2dcdce92d86a034d69d65df9b256e/pre_commit-4.6.1.tar.gz", hash = "sha256:03e809865c7d178b9979d06c761fcbfe6808fdaded8581a745bb110e52050421", size = 198646, upload-time = "2026-07-21T20:56:58.225Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/49/bc925106abcdac498074f2cbe6137e94e09f418dd2b7775df5b577dc0313/pre_commit-4.6.1-py2.py3-none-any.whl", hash = "sha256:0e3b2942510d1fb34eec167a3ec57331bf8442122f1153a9fb8b58f5c49b2717", size = 226186, upload-time = "2026-07-21T20:56:57.064Z" }, +] + +[[package]] +name = "prompt-toolkit" +version = "3.0.52" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "wcwidth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/96/06e01a7b38dce6fe1db213e061a4602dd6032a8a97ef6c1a862537732421/prompt_toolkit-3.0.52.tar.gz", hash = "sha256:28cde192929c8e7321de85de1ddbe736f1375148b02f2e17edd840042b1be855", size = 434198, upload-time = "2025-08-27T15:24:02.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/03/0d3ce49e2505ae70cf43bc5bb3033955d2fc9f932163e84dc0779cc47f48/prompt_toolkit-3.0.52-py3-none-any.whl", hash = "sha256:9aac639a3bbd33284347de5ad8d68ecc044b91a762dc39b7c21095fcd6a19955", size = 391431, upload-time = "2025-08-27T15:23:59.498Z" }, +] + +[[package]] +name = "psutil" +version = "7.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, +] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/e5/16ff212c1e452235a90aeb09066144d0c5a6a8c0834397e03f5224495c4e/ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220", size = 70762, upload-time = "2020-12-28T15:15:30.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35", size = 13993, upload-time = "2020-12-28T15:15:28.35Z" }, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/05/0a34433a064256a578f1783a10da6df098ceaa4a57bbeaa96a6c0352786b/pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42", size = 19752, upload-time = "2024-07-21T12:58:21.801Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842, upload-time = "2024-07-21T12:58:20.04Z" }, +] + +[[package]] +name = "pydantic" +version = "2.13.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/a5/b60d21ac674192f8ab0ba4e9fd860690f9b4a6e51ca5df118733b487d8d6/pydantic-2.13.4.tar.gz", hash = "sha256:c40756b57adaa8b1efeeced5c196f3f3b7c435f90e84ea7f443901bec8099ef6", size = 844775, upload-time = "2026-05-06T13:43:05.343Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/7b/122376b1fd3c62c1ed9dc80c931ace4844b3c55407b6fb2d199377c9736f/pydantic-2.13.4-py3-none-any.whl", hash = "sha256:45a282cde31d808236fd7ea9d919b128653c8b38b393d1c4ab335c62924d9aba", size = 472262, upload-time = "2026-05-06T13:43:02.641Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.46.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/56/921726b776ace8d8f5db44c4ef961006580d91dc52b803c489fafd1aa249/pydantic_core-2.46.4.tar.gz", hash = "sha256:62f875393d7f270851f20523dd2e29f082bcc82292d66db2b64ea71f64b6e1c1", size = 471464, upload-time = "2026-05-06T13:37:06.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/08/f1ba952f1c8ae5581c70fa9c6da89f247b83e3dd8c09c035d5d7931fc23d/pydantic_core-2.46.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:a396dcc17e5a0b164dbe026896245a4fa9ff402edca1dff0be3d53a517f74de4", size = 2113146, upload-time = "2026-05-06T13:37:36.537Z" }, + { url = "https://files.pythonhosted.org/packages/56/c6/65f646c7ff09bd257f660434adb45c4dfcbbcebcc030562fecf6f5bf887d/pydantic_core-2.46.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:da4b951fe36dc7c3a1ccb4e3cd1747c3542b8c9ceede8fc86cae054e764485f5", size = 1949769, upload-time = "2026-05-06T13:37:46.365Z" }, + { url = "https://files.pythonhosted.org/packages/64/ba/bfb1d928fd5b49e1258935ff104ae356e9fd89384a55bf9f847e9193ad40/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb63e0198ca18aad131c089b9204c23079c3afa95487e561f4c522d519e55aba", size = 1974958, upload-time = "2026-05-06T13:37:28.611Z" }, + { url = "https://files.pythonhosted.org/packages/4e/74/76223bfb117b64af743c9b6670d1364516f5c0604f96b48f3272f6af6cc6/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f47286a97f0bc9b8859519809077b91b2cefe4ae47fcbf5e466a009c1c5d742b", size = 2042118, upload-time = "2026-05-06T13:36:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/cb/7b/848732968bc8f48f3187542f08358b9d842db564147b256669426ebb1652/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:905a0ed8ea6f2d61c1738835f99b699348d7857379083e5fc497fa0c967a407c", size = 2222876, upload-time = "2026-05-06T13:38:25.455Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2f/e90b63ee2e14bd8d3db8f705a6d75d64e6ee1b7c2c8833747ce706e1e0ce/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea793e075b70290d89d8142074262885d3f7da19634845135751bd6344f73b50", size = 2286703, upload-time = "2026-05-06T13:37:53.304Z" }, + { url = "https://files.pythonhosted.org/packages/ba/1e/acc4d70f88a0a277e4a1fa77ebb985ceabaf900430f875bf9338e11c9420/pydantic_core-2.46.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aebd9183f9d112f569aeb5b2214d1a10a33bec8456447f7fbdfa51d38d4cd", size = 2092042, upload-time = "2026-05-06T13:38:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/a9/da/0a422b57bf8504102bf3c4ccea9c41bab5a5cee6a54650acf8faf67f5a24/pydantic_core-2.46.4-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:b078afbc25f3a1436c7a1d2cd3e322497ee99615ba97c563566fdf46aff1ee01", size = 2117231, upload-time = "2026-05-06T13:39:23.146Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2a/2ac13c3af305843e23c5078c53d135656b3f05a2fd78cb7bbbb12e97b473/pydantic_core-2.46.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f747929cf940cddb5b3668a390056ddd5ba2e5010615ea2dcf4f9c4f3ab8791d", size = 2168388, upload-time = "2026-05-06T13:40:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/72/04/2beacf7e1607e93eefe4aed1b4709f079b905fb77530179d4f7c71745f22/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:daa27d92c36f24388fe3ad306b174781c747627f134452e4f128ea00ce1fe8c4", size = 2184769, upload-time = "2026-05-06T13:38:13.901Z" }, + { url = "https://files.pythonhosted.org/packages/9e/29/d2b9fd9f539133548eaf622c06a4ce176cb46ac59f32d0359c4abc0de047/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:19e51f073cd3df251856a8a4189fbdf1de4012c3ebacfb1884f94f1eb406079f", size = 2319312, upload-time = "2026-05-06T13:39:08.24Z" }, + { url = "https://files.pythonhosted.org/packages/7c/af/0f7a5b85fec6075bea96e3ef9187de38fccced0de92c1e7feda8d5cc7bb9/pydantic_core-2.46.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1747f85cee84c26985853c6f3d9bd3e75da5212912443fa111c113b9c246f39", size = 2361817, upload-time = "2026-05-06T13:38:43.2Z" }, + { url = "https://files.pythonhosted.org/packages/25/a4/73363fec545fd3ec025490bdda2743c56d0dd5b6266b1a53bbe9e4265375/pydantic_core-2.46.4-cp310-cp310-win32.whl", hash = "sha256:2f84c03c8607173d16b5a854ec68a2f9079ae03237a54fb506d13af47e1d018d", size = 1987085, upload-time = "2026-05-06T13:39:25.497Z" }, + { url = "https://files.pythonhosted.org/packages/01/aa/62f082da2c91fac1c234bc9ee0066257ce83f0604abd72e4c9d5991f2d84/pydantic_core-2.46.4-cp310-cp310-win_amd64.whl", hash = "sha256:8358a950c8909158e3df31538a7e4edc2d7265a7c54b47f0864d9e5bae9dcebf", size = 2074311, upload-time = "2026-05-06T13:39:59.922Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/6d7708d2cfc1a832acb6aeb0cd16e801902df8a0f583bb3b4b527fde022e/pydantic_core-2.46.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:0e96592440881c74a213e5ad528e2b24d3d4f940de2766bed9010ab1d9e51594", size = 2111872, upload-time = "2026-05-06T13:40:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6f/aa064a3e74b5745afbdf250594f38e7ead05e2d651bcb35994b9417a0d4d/pydantic_core-2.46.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0d65b8c354be7fb5f720c3caa8bc940bc2d20ce749c8e06135f07f8ed95dd7c", size = 1948255, upload-time = "2026-05-06T13:39:12.574Z" }, + { url = "https://files.pythonhosted.org/packages/43/3a/41114a9f7569b84b4d84e7a018c57c56347dac30c0d4a872946ec4e36c46/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bfb192b3f4b9e8a89b6277b6ce787564f62cfd272055f6e685726b111dc7826", size = 1972827, upload-time = "2026-05-06T13:38:19.841Z" }, + { url = "https://files.pythonhosted.org/packages/ef/25/1ab42e8048fe551934d9884e8d64daa7e990ad386f310a15981aeb6a5b08/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9037063db01f09b09e237c282b6792bd4da634b5402c4e7f0c61effed7701a04", size = 2041051, upload-time = "2026-05-06T13:38:10.447Z" }, + { url = "https://files.pythonhosted.org/packages/94/c2/1a934597ddf08da410385b3b7aae91956a5a76c635effef456074fad7e88/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fc010ab034c8c7452522748bf937df58020d256ccae0874463d1f4d01758af8e", size = 2221314, upload-time = "2026-05-06T13:40:13.089Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/9e8ad178c9c4df27ad3c8f25d1fe2a7ab0d2ba0559fad4aee5d3d1f16771/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5dac79fa1614d1e06ca695109c6105923bd9c7d1d6c918d4e637b7e6b32fd3", size = 2285146, upload-time = "2026-05-06T13:38:59.224Z" }, + { url = "https://files.pythonhosted.org/packages/80/50/540cd3aeefc041beb111125c4bff779831a2111fc6b15a9138cda277d32c/pydantic_core-2.46.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9fa868638bf362d3d138ea55829cefb3d5f4b0d7f142234382a15e2485dbec4", size = 2089685, upload-time = "2026-05-06T13:38:17.762Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a4/b440ad35f05f6a38f89fa0f149accb3f0e02be94ca5e15f3c449a61b4bc9/pydantic_core-2.46.4-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:17299feefe090f2caa5b8e37222bb5f663e4935a8bfa6931d4102e5df1a9f398", size = 2115420, upload-time = "2026-05-06T13:37:58.195Z" }, + { url = "https://files.pythonhosted.org/packages/99/61/de4f55db8dfd57bfdfa9a12ec90fe1b57c4f41062f7ca86f08586b3e0ac0/pydantic_core-2.46.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c63ebc82684aa89d9a3bcbd13d515b3be44250dc68dd3bd81526c1cb31286c3", size = 2165122, upload-time = "2026-05-06T13:37:01.167Z" }, + { url = "https://files.pythonhosted.org/packages/f7/52/7c529d7bdb2d1068bd52f51fe32572c8301f9a4febf1948f10639f1436f5/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:aaa2a54443eff1950ba5ddc6b6ccda0d9c84a364276a62f969bdf2a390650848", size = 2182573, upload-time = "2026-05-06T13:38:45.04Z" }, + { url = "https://files.pythonhosted.org/packages/37/b3/7c40325848ba78247f2812dcf9c7274e38cd801820ca6dd9fe63bcfb0eb4/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:18e5ceec2ab67e6d5f1a9085e5a24c9c4e2ac4545730bfe668680bca05e555f3", size = 2317139, upload-time = "2026-05-06T13:37:15.539Z" }, + { url = "https://files.pythonhosted.org/packages/d9/37/f913f81a657c865b75da6c0dbed79876073c2a43b5bd9edbe8da785e4d49/pydantic_core-2.46.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a0f62d0a58f4e7da165457e995725421e0064f2255d8eccebc49f41bbc23b109", size = 2360433, upload-time = "2026-05-06T13:37:30.099Z" }, + { url = "https://files.pythonhosted.org/packages/c4/67/6acaa1be2567f9256b056d8477158cac7240813956ce86e49deae8e173b4/pydantic_core-2.46.4-cp311-cp311-win32.whl", hash = "sha256:041bde0a48fd37cf71cab1c9d56d3e8625a3793fef1f7dd232b3ff37e978ecda", size = 1985513, upload-time = "2026-05-06T13:38:15.669Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e6/c505f83dfeda9a2e5c995cfd872949e4d05e12f7feb3dca72f633daefa94/pydantic_core-2.46.4-cp311-cp311-win_amd64.whl", hash = "sha256:6f2eeda33a839975441c86a4119e1383c50b47faf0cbb5176985565c6bb02c33", size = 2071114, upload-time = "2026-05-06T13:40:35.416Z" }, + { url = "https://files.pythonhosted.org/packages/0f/da/7a263a96d965d9d0df5e8de8a475f33495451117035b09acb110288c381f/pydantic_core-2.46.4-cp311-cp311-win_arm64.whl", hash = "sha256:14f4c5d6db102bd796a627bbb3a17b4cf4574b9ae861d8b7c9a9661c6dd3362d", size = 2044298, upload-time = "2026-05-06T13:38:29.754Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8c/af022f0af448d7747c5154288d46b5f2bc5f17366eaa0e23e9aa04d59f3b/pydantic_core-2.46.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3245406455a5d98187ec35530fd772b1d799b26667980872c8d4614991e2c4a2", size = 2106158, upload-time = "2026-05-06T13:38:57.215Z" }, + { url = "https://files.pythonhosted.org/packages/19/95/6195171e385007300f0f5574592e467c568becce2d937a0b6804f218bc49/pydantic_core-2.46.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:962ccbab7b642487b1d8b7df90ef677e03134cf1fd8880bf698649b22a69371f", size = 1951724, upload-time = "2026-05-06T13:37:02.697Z" }, + { url = "https://files.pythonhosted.org/packages/8e/bc/f47d1ff9cbb1620e1b5b697eef06010035735f07820180e74178226b27b3/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8233f2947cf85404441fd7e0085f53b10c93e0ee78611099b5c7237e36aacbf7", size = 1975742, upload-time = "2026-05-06T13:37:09.448Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/9b9a5b0306345664a2da6410877af6e8082481b5884b3ddd78d47c6013ce/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a233125ac121aa3ffba9a2b59edfc4a985a76092dc8279586ab4b71390875e7", size = 2052418, upload-time = "2026-05-06T13:37:38.234Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b7/a65fec226f5d78fc39f4a13c4cc0c768c22b113438f60c14adc9d2865038/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b712b53160b79a5850310b912a5ef8e57e56947c8ad690c227f5c9d7e561712", size = 2232274, upload-time = "2026-05-06T13:38:27.753Z" }, + { url = "https://files.pythonhosted.org/packages/68/f0/92039db98b907ef49269a8271f67db9cb78ae2fc68062ef7e4e77adb5f61/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9401557acd873c3a7f3eb9383edef8ac4968f9510e340f4808d427e75667e7b4", size = 2309940, upload-time = "2026-05-06T13:38:05.353Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/2aab507d3d00ca626e8e57c1eac6a79e4e5fbcc63eb99733ff55d1717f65/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:926c9541b14b12b1681dca8a0b75feb510b06c6341b70a8e500c2fdcff837cce", size = 2094516, upload-time = "2026-05-06T13:39:10.577Z" }, + { url = "https://files.pythonhosted.org/packages/22/37/a8aca44d40d737dde2bc05b3c6c07dff0de07ce6f82e9f3167aeaf4d5dea/pydantic_core-2.46.4-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:56cb4851bcaf3d117eddcef4fe66afd750a50274b0da8e22be256d10e5611987", size = 2136854, upload-time = "2026-05-06T13:40:22.59Z" }, + { url = "https://files.pythonhosted.org/packages/24/99/fcef1b79238c06a8cbec70819ac722ba76e02bc8ada9b0fd66eba40da01b/pydantic_core-2.46.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c68fcd102d71ea85c5b2dfac3f4f8476eff42a9e078fd5faefff6d145063536b", size = 2180306, upload-time = "2026-05-06T13:40:10.666Z" }, + { url = "https://files.pythonhosted.org/packages/ae/6c/fc44000918855b42779d007ae63b0532794739027b2f417321cddbc44f6a/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b2f69dec1725e79a012d920df1707de5caf7ed5e08f3be4435e25803efc47458", size = 2190044, upload-time = "2026-05-06T13:40:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/6b/65/d9cadc9f1920d7a127ad2edba16c1db7916e59719285cd6c94600b0080ba/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:8d0820e8192167f80d88d64038e609c31452eeca865b4e1d9950a27a4609b00b", size = 2329133, upload-time = "2026-05-06T13:39:57.365Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cf/c873d91679f3a30bcf5e7ac280ce5573483e72295307685120d0d5ad3416/pydantic_core-2.46.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fbdb89b3e1c94a30cc5edfce477c6e6a5dc4d8f84665b455c27582f211a1c72c", size = 2374464, upload-time = "2026-05-06T13:38:06.976Z" }, + { url = "https://files.pythonhosted.org/packages/47/bd/6f2fc8188f31bf10590f1e98e7b306336161fac930a8c514cd7bd828c7dc/pydantic_core-2.46.4-cp312-cp312-win32.whl", hash = "sha256:9aa768456404a8bf48a4406685ac2bec8e72b62c69313734fa3b73cf33b3a894", size = 1974823, upload-time = "2026-05-06T13:40:47.985Z" }, + { url = "https://files.pythonhosted.org/packages/40/8c/985c1d41ea1107c2534abd9870e4ed5c8e7669b5c308297835c001e7a1c4/pydantic_core-2.46.4-cp312-cp312-win_amd64.whl", hash = "sha256:e9c26f834c65f5752f3f06cb08cb86a913ceb7274d0db6e267808a708b46bc89", size = 2072919, upload-time = "2026-05-06T13:39:21.153Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ba/f463d006e0c47373ca7ec5e1a261c59dc01ef4d62b2657af925fb0deee3a/pydantic_core-2.46.4-cp312-cp312-win_arm64.whl", hash = "sha256:4fc73cb559bdb54b1134a706a2802a4cddd27a0633f5abb7e53056268751ac6a", size = 2027604, upload-time = "2026-05-06T13:39:03.753Z" }, + { url = "https://files.pythonhosted.org/packages/51/a2/5d30b469c5267a17b39dec53208222f76a8d351dfac4af661888c5aee77d/pydantic_core-2.46.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5d5902252db0d3cedf8d4a1bc68f70eeb430f7e4c7104c8c476753519b423008", size = 2106306, upload-time = "2026-05-06T13:37:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c1/81/4fa520eaffa8bd7d1525e644cd6d39e7d60b1592bc5b516693c7340b50f1/pydantic_core-2.46.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c94f0688e7b8d0a67abf40e57a7eaaecd17cc9586706a31b76c031f63df052b4", size = 1951906, upload-time = "2026-05-06T13:37:17.012Z" }, + { url = "https://files.pythonhosted.org/packages/03/d5/fd02da45b659668b05923b17ba3a0100a0a3d5541e3bd8fcc4ecb711309e/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f027324c56cd5406ca49c124b0db10e56c69064fec039acc571c29020cc87c76", size = 1976802, upload-time = "2026-05-06T13:37:35.113Z" }, + { url = "https://files.pythonhosted.org/packages/21/f2/95727e1368be3d3ed485eaab7adbd7dda408f33f7a36e8b48e0144002b91/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e739fee756ba1010f8bcccb534252e85a35fe45ae92c295a06059ce58b74ccd3", size = 2052446, upload-time = "2026-05-06T13:37:12.313Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/5d99feea3f77c7234b8718075b23db11532773c1a0dbd9b9490215dc2eeb/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d56801be94b86a9da183e5f3766e6310752b99ff647e38b09a9500d88e46e76", size = 2232757, upload-time = "2026-05-06T13:39:01.149Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3a/508ac615935ef7588cf6d9e9b91309fdc2da751af865e02a9098de88258c/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2412e734dcb48da14d4e4006b82b46b74f2518b8a26ee7e58c6844a6cd6d03c4", size = 2309275, upload-time = "2026-05-06T13:37:41.406Z" }, + { url = "https://files.pythonhosted.org/packages/07/f8/41db9de19d7987d6b04715a02b3b40aea467000275d9d758ffaa31af7d50/pydantic_core-2.46.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9551187363ffc0de2a00b2e47c25aeaeb1020b69b668762966df15fc5659dd5a", size = 2094467, upload-time = "2026-05-06T13:39:18.847Z" }, + { url = "https://files.pythonhosted.org/packages/2c/e2/f35033184cb11d0052daf4416e8e10a502ea2ac006fc4f459aee872727d1/pydantic_core-2.46.4-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0186750b482eefa11d7f435892b09c5c606193ef3375bcf94aa00ae6bfb66262", size = 2134417, upload-time = "2026-05-06T13:40:17.944Z" }, + { url = "https://files.pythonhosted.org/packages/7e/7b/6ceeb1cc90e193862f444ebe373d8fdf613f0a82572dde03fb10734c6c71/pydantic_core-2.46.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5855698a4856556d86e8e6cd8434bc3ac0314ee8e12089ae0e143f64c6256e4e", size = 2179782, upload-time = "2026-05-06T13:40:32.618Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f2/c8d7773ede6af08036423a00ae0ceffce266c3c52a096c435d68c896083f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:cbaf13819775b7f769bf4a1f066cb6df7a28d4480081a589828ef190226881cd", size = 2188782, upload-time = "2026-05-06T13:36:51.018Z" }, + { url = "https://files.pythonhosted.org/packages/59/31/0c864784e31f09f05cdd87606f08923b9c9e7f6e51dd27f20f62f975ce9f/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:633147d34cf4550417f12e2b1a0383973bdf5cdfde212cb09e9a581cf10820be", size = 2328334, upload-time = "2026-05-06T13:40:37.764Z" }, + { url = "https://files.pythonhosted.org/packages/c2/eb/4f6c8a41efa30baa755590f4141abf3a8c370fab610915733e74134a7270/pydantic_core-2.46.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:82cf5301172168103724d49a1444d3378cb20cdee30b116a1bd6031236298a5d", size = 2372986, upload-time = "2026-05-06T13:39:34.152Z" }, + { url = "https://files.pythonhosted.org/packages/5b/24/b375a480d53113860c299764bfe9f349a3dc9108b3adc0d7f0d786492ebf/pydantic_core-2.46.4-cp313-cp313-win32.whl", hash = "sha256:9fa8ae11da9e2b3126c6426f147e0fba88d96d65921799bb30c6abd1cb2c97fb", size = 1973693, upload-time = "2026-05-06T13:37:55.072Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e8/cff247591966f2d22ec8c003cd7587e27b7ba7b81ab2fb888e3ab75dc285/pydantic_core-2.46.4-cp313-cp313-win_amd64.whl", hash = "sha256:6b3ace8194b0e5204818c92802dcdca7fc6d88aabbb799d7c795540d9cd6d292", size = 2071819, upload-time = "2026-05-06T13:38:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1a/f4aee670d5670e9e148e0c82c7db98d780be566c6e6a97ee8035528ca0b3/pydantic_core-2.46.4-cp313-cp313-win_arm64.whl", hash = "sha256:184c081504d17f1c1066e430e117142b2c77d9448a97f7b65c6ac9fd9aee238d", size = 2027411, upload-time = "2026-05-06T13:40:45.796Z" }, + { url = "https://files.pythonhosted.org/packages/8d/74/228a26ddad29c6672b805d9fd78e8d251cd04004fa7eed0e622096cd0250/pydantic_core-2.46.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:428e04521a40150c85216fc8b85e8d39fece235a9cf5e383761238c7fa9b96fb", size = 2102079, upload-time = "2026-05-06T13:38:41.019Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/8970b150a4b4365623ae00fc88603491f763c627311ae8031e3111356d6e/pydantic_core-2.46.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:23ace664830ee0bfe014a0c7bc248b1f7f25ed7ad103852c317624a1083af462", size = 1952179, upload-time = "2026-05-06T13:36:59.812Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/5211a831ae054928054b2f79731661087a2bc5c01e825c672b3a4a8f1b3e/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce5c1d2a8b27468f433ca974829c44060b8097eedc39933e3c206a90ee49c4a9", size = 1978926, upload-time = "2026-05-06T13:37:39.933Z" }, + { url = "https://files.pythonhosted.org/packages/57/e9/689668733b1eb67adeef047db3c2e8788fcf65a7fd9c9e2b46b7744fe245/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7283d57845ecf5a163403eb0702dfc220cc4fbdd18919cb5ccea4f95ee1cdab4", size = 2046785, upload-time = "2026-05-06T13:38:01.995Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/6715260422ff50a2109878fd24d948a6c3446bb2664f34ee78cd972b3acd/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8daafc69c93ee8a0204506a3b6b30f586ef54028f52aeeeb5c4cfc5184fd5914", size = 2228733, upload-time = "2026-05-06T13:40:50.371Z" }, + { url = "https://files.pythonhosted.org/packages/18/ae/fdb2f64316afca925640f8e70bb1a564b0ec2721c1389e25b8eb4bf9a299/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd2213145bcc2ba85884d0ac63d222fece9209678f77b9b4d76f054c561adb28", size = 2307534, upload-time = "2026-05-06T13:37:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/89/1d/8eff589b45bb8190a9d12c49cfad0f176a5cbd1534908a6b5125e2886239/pydantic_core-2.46.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5f930472650a82629163023e630d160863fce524c616f4e5186e5de9d9a49b", size = 2099732, upload-time = "2026-05-06T13:39:31.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/d5/ee5a3366637fee41dee51a1fc91562dcf12ddbc68fda34e6b253da2324bb/pydantic_core-2.46.4-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:c1b3f518abeca3aa13c712fd202306e145abf59a18b094a6bafb2d2bbf59192c", size = 2129627, upload-time = "2026-05-06T13:37:25.033Z" }, + { url = "https://files.pythonhosted.org/packages/94/33/2414be571d2c6a6c4d08be21f9292b6d3fdb08949a97b6dfe985017821db/pydantic_core-2.46.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a7dd0b3ee80d90150e3495a3a13ac34dbcbfd4f012996a6a1d8900e91b5c0fb", size = 2179141, upload-time = "2026-05-06T13:37:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/7b/79/7daa95be995be0eecc4cf75064cb33f9bbbfe3fe0158caf2f0d4a996a5c7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:3fb702cd90b0446a3a1c5e470bfa0dd23c0233b676a9099ddcc964fa6ca13898", size = 2184325, upload-time = "2026-05-06T13:36:53.615Z" }, + { url = "https://files.pythonhosted.org/packages/9f/cb/d0a382f5c0de8a222dc61c65348e0ce831b1f68e0a018450d31c2cace3a5/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b8458003118a712e66286df6a707db01c52c0f52f7db8e4a38f0da1d3b94fc4e", size = 2323990, upload-time = "2026-05-06T13:40:29.971Z" }, + { url = "https://files.pythonhosted.org/packages/05/db/d9ba624cc4a5aced1598e88c04fdbd8310c8a69b9d38b9a3d39ce3a61ed7/pydantic_core-2.46.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:372429a130e469c9cd698925ce5fc50940b7a1336b0d82038e63d5bbc4edc519", size = 2369978, upload-time = "2026-05-06T13:37:23.027Z" }, + { url = "https://files.pythonhosted.org/packages/f2/20/d15df15ba918c423461905802bfd2981c3af0bfa0e40d05e13edbfa48bc3/pydantic_core-2.46.4-cp314-cp314-win32.whl", hash = "sha256:85bb3611ff1802f3ee7fdd7dbff26b56f343fb432d57a4728fdd49b6ef35e2f4", size = 1966354, upload-time = "2026-05-06T13:38:03.499Z" }, + { url = "https://files.pythonhosted.org/packages/fc/b6/6b8de4c0a7d7ab3004c439c80c5c1e0a3e8d78bbae19379b01960383d9e5/pydantic_core-2.46.4-cp314-cp314-win_amd64.whl", hash = "sha256:811ff8e9c313ab425368bcbb36e5c4ebd7108c2bbf4e4089cfbb0b01eff63fac", size = 2072238, upload-time = "2026-05-06T13:39:40.807Z" }, + { url = "https://files.pythonhosted.org/packages/32/36/51eb763beec1f4cf59b1db243a7dcc39cbb41230f050a09b9d69faaf0a48/pydantic_core-2.46.4-cp314-cp314-win_arm64.whl", hash = "sha256:bfec22eab3c8cc2ceec0248aec886624116dc079afa027ecc8ad4a7e62010f8a", size = 2018251, upload-time = "2026-05-06T13:37:26.72Z" }, + { url = "https://files.pythonhosted.org/packages/e8/91/855af51d625b23aa987116a19e231d2aaef9c4a415273ddc189b79a45fee/pydantic_core-2.46.4-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:af8244b2bef6aaad6d92cda81372de7f8c8d36c9f0c3ea36e827c60e7d9467a0", size = 2099593, upload-time = "2026-05-06T13:39:47.682Z" }, + { url = "https://files.pythonhosted.org/packages/fb/1b/8784a54c65edb5f49f0a14d6977cf1b209bba85a4c77445b255c2de58ab3/pydantic_core-2.46.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a4330cdbc57162e4b3aa303f588ba752257694c9c9be3e7ebb11b4aca659b5d", size = 1935226, upload-time = "2026-05-06T13:40:40.428Z" }, + { url = "https://files.pythonhosted.org/packages/e8/e7/1955d28d1afc56dd4b3ad7cc0cf39df1b9852964cf16e5d13912756d6d6b/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c61fc04a3d840155ff08e475a04809278972fe6aef51e2720554e96367e34b", size = 1974605, upload-time = "2026-05-06T13:37:32.029Z" }, + { url = "https://files.pythonhosted.org/packages/93/e2/3fedbf0ba7a22850e6e9fd78117f1c0f10f950182344d8a6c535d468fdd8/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c50f2528cf200c5eed56faf3f4e22fcd5f38c157a8b78576e6ba3168ec35f000", size = 2030777, upload-time = "2026-05-06T13:38:55.239Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/46be275fcaaba0b4f5b9669dd852267ce1ff616592dccf7a7845588df091/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0cbe8b01f948de4286c74cdd6c667aceb38f5c1e26f0693b3983d9d74887c65e", size = 2236641, upload-time = "2026-05-06T13:37:08.096Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/12e93e46a8bac9988be3c016860f83293daea8c716c029c9ace279036f2f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:617d7e2ca7dcb8c5cf6bcb8c59b8832c94b36196bbf1cbd1bfb56ed341905edd", size = 2286404, upload-time = "2026-05-06T13:40:20.221Z" }, + { url = "https://files.pythonhosted.org/packages/e2/4a/4d8b19008f38d31c53b8219cfedc2e3d5de5fe99d90076b7e767de29274f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7027560ee92211647d0d34e3f7cd6f50da56399d26a9c8ad0da286d3869a53f3", size = 2109219, upload-time = "2026-05-06T13:38:12.153Z" }, + { url = "https://files.pythonhosted.org/packages/88/70/3cbc40978fefb7bb09c6708d40d4ad1a5d70fd7213c3d17f971de868ec1f/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:f99626688942fb746e545232e7726926f3be91b5975f8b55327665fafda991c7", size = 2110594, upload-time = "2026-05-06T13:40:02.971Z" }, + { url = "https://files.pythonhosted.org/packages/9d/20/b8d36736216e29491125531685b2f9e61aa5b4b2599893f8268551da3338/pydantic_core-2.46.4-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3e9034a63de20e15e8ade85358bc6efc614008cab72898b4b4952bea0509ff", size = 2159542, upload-time = "2026-05-06T13:39:27.506Z" }, + { url = "https://files.pythonhosted.org/packages/1d/a2/367df868eb584dacf6bf82a389272406d7178e301c4ac82545ab98bc2dd9/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:97e7cf2be5c77b7d1a9713a05605d49460d02c6078d38d8bef3cbe323c548424", size = 2168146, upload-time = "2026-05-06T13:38:31.93Z" }, + { url = "https://files.pythonhosted.org/packages/c1/b8/4460f77f7e201893f649a29ab355dddd3beee8a97bcb1a320db414f9a06e/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:3bf92c5d0e00fefaab325a4d27828fe6b6e2a21848686b5b60d2d9eeb09d76c6", size = 2306309, upload-time = "2026-05-06T13:37:44.717Z" }, + { url = "https://files.pythonhosted.org/packages/64/c4/be2639293acd87dc8ddbcec41a73cee9b2ebf996fe6d892a1a74e88ad3f7/pydantic_core-2.46.4-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:3ecbc122d18468d06ca279dc26a8c2e2d5acb10943bb35e36ae92096dc3b5565", size = 2369736, upload-time = "2026-05-06T13:37:05.645Z" }, + { url = "https://files.pythonhosted.org/packages/30/a6/9f9f380dbb301f67023bf8f707aaa75daadf84f7152d95c410fd7e81d994/pydantic_core-2.46.4-cp314-cp314t-win32.whl", hash = "sha256:e846ae7835bf0703ae43f534ab79a867146dadd59dc9ca5c8b53d5c8f7c9ef02", size = 1955575, upload-time = "2026-05-06T13:38:51.116Z" }, + { url = "https://files.pythonhosted.org/packages/40/1f/f1eb9eb350e795d1af8586289746f5c5677d16043040d63710e22abc43c9/pydantic_core-2.46.4-cp314-cp314t-win_amd64.whl", hash = "sha256:2108ba5c1c1eca18030634489dc544844144ee36357f2f9f780b93e7ddbb44b5", size = 2051624, upload-time = "2026-05-06T13:38:21.672Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, + { url = "https://files.pythonhosted.org/packages/5d/00/13a0c039569d1e583779ee1b8d7df6bfe275a0db83fcae14f01d6856c16e/pydantic_core-2.46.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fd8b3d9fd264be37976686c7f65cd52a83f5e84f4bfd2adf9c1d469676bbb6ae", size = 2115337, upload-time = "2026-05-06T13:38:37.741Z" }, + { url = "https://files.pythonhosted.org/packages/41/60/e70fa1ee03e243bdfd4b1fddf1e1f2a8fba681df3034b51b9376c0fb5bf5/pydantic_core-2.46.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9f444c499b3eefd3a92e348059471ea0c3a6e303d9c1cec09fa748fd9f895201", size = 1957976, upload-time = "2026-05-06T13:37:33.478Z" }, + { url = "https://files.pythonhosted.org/packages/11/9a/78fb5f2ea849f767ea802de8b4e8f5a0c4a48ddbe4bc66bd19ac2f55a01c/pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3447661d99f75a3683a4cf5c87da72f2161964611864dbbeac7fbb118bb4bfc0", size = 1979390, upload-time = "2026-05-06T13:36:52.419Z" }, + { url = "https://files.pythonhosted.org/packages/f5/7d/3acfdcd000bad9735de0430a88355948469781f62cb841fd63e8a307e80e/pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b9bab013d1c7a79d3501ff86d0bc9c31bf587db4551677b96bec07df78c6b15", size = 2043263, upload-time = "2026-05-06T13:39:54.798Z" }, + { url = "https://files.pythonhosted.org/packages/35/60/1325e5a8d7f9697416481c7f7c1c304738d6b961a7fd1ea0f054ce0f14fb/pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d995260fdf4e1db774581b4900e0f832abe3c7c84996726bbc161b19c8f29e76", size = 2225708, upload-time = "2026-05-06T13:40:24.887Z" }, + { url = "https://files.pythonhosted.org/packages/6a/b0/9ec8c38f33b26db0b612cb7fd165bb0a370773710432a2a74fa31287b430/pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13a646d65d09fbf1bc6b3a9635d30095c8e7e5cc419ff35ecc563c5fd04cd49", size = 2288494, upload-time = "2026-05-06T13:38:00.091Z" }, + { url = "https://files.pythonhosted.org/packages/65/05/497446a9586d1b2d24ee25ebe208beb15388f1875d783e1e014055d150ac/pydantic_core-2.46.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432c179df7874eeb73307aad2df0755e1ae0efa61ff0ea89b93e194411ae3928", size = 2095629, upload-time = "2026-05-06T13:38:23.632Z" }, + { url = "https://files.pythonhosted.org/packages/93/d9/cd5fa98f9d94f9294c15459396c8a2383c164469e679ac178d6d42cfee6b/pydantic_core-2.46.4-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:e68b7a074f65a2fd746c52a7ce6142ab7006074ac269ace0c25cd8ba171f8066", size = 2119309, upload-time = "2026-05-06T13:39:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/20/1b/64cec655451ddbf3976df5dc9706b240df4fdaebdeebeadd4f59a8dab926/pydantic_core-2.46.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4a05d69cba51d852c5c3e92758653245a50c0b646ced0cf05bd793ed592839d6", size = 2170216, upload-time = "2026-05-06T13:39:14.561Z" }, + { url = "https://files.pythonhosted.org/packages/2a/21/fe9f039138c9ea3be10ccdb6ec490acb54dcbef5a5e96dbdf1411f82b929/pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:228ee9bae8bef5b1e97ec58302f80357c37199e0d0a99174e138d28e6957b9d9", size = 2186726, upload-time = "2026-05-06T13:37:51.597Z" }, + { url = "https://files.pythonhosted.org/packages/44/cb/19ca0da64821d1aefcef65f253aa9ecbdd0dde360f607d0f9b3d95db2b4e/pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:10e17cbb10a330363733efc4d7c4d0dd827ac0909b8f6a6542298fed1ea62f29", size = 2320400, upload-time = "2026-05-06T13:39:36.29Z" }, + { url = "https://files.pythonhosted.org/packages/cd/14/fe3fbf6e845bf2080dc2f282d75085ddf79d037b35634ecde68f33c217b4/pydantic_core-2.46.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:91a06d2e259ecfbd8c901d70c3c507900458498142b3026a296b7de4d1322cc9", size = 2363318, upload-time = "2026-05-06T13:38:53.039Z" }, + { url = "https://files.pythonhosted.org/packages/62/88/60b110889507a426eecf626f7536566cb290ada71147eff49b6e2724ca62/pydantic_core-2.46.4-cp39-cp39-win32.whl", hash = "sha256:d80ee3d731373b24cebbc10d689ca4ee1875caf0d5703a245db18efd4dd37fc1", size = 1988880, upload-time = "2026-05-06T13:39:16.572Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d6/8ede2f98f17e1e4e127d37be0eced4eee931a511c62cd68af50e1b25bfa9/pydantic_core-2.46.4-cp39-cp39-win_amd64.whl", hash = "sha256:3be77f45df024d789a672ae34f8b06fb346c4f9f46ea714956660ea4862e89ac", size = 2079257, upload-time = "2026-05-06T13:39:38.498Z" }, + { url = "https://files.pythonhosted.org/packages/ee/a4/73995fd4ebbb46ba0ee51e6fa049b8f02c40daebb762208feda8a6b7894d/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:14d4edf427bdcf950a8a02d7cb44a08614388dd6e1bdcbf4f67504fa7887da9c", size = 2111589, upload-time = "2026-05-06T13:37:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7f/f37d3a5e8bfcc2e403f5c57a730f2d815693fb42119e8ea48b3789335af1/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:0ce40cd7b21210e99342afafbd4d0f76d784eb5b1d60f3bdc566be4983c6c73b", size = 1944552, upload-time = "2026-05-06T13:36:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/15/3c/d7eb777b3ff43e8433a4efb39a17aa8fd98a4ee8561a24a67ef5db07b2d6/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90884113d8b48f760e9587002789ddd741e76ab9f89518cd1e43b1f1a52ec44b", size = 1982984, upload-time = "2026-05-06T13:39:06.207Z" }, + { url = "https://files.pythonhosted.org/packages/63/87/70b9f40170a81afd55ca26c9b2acb25c20d64bcfbf888fafecb3ba077d4c/pydantic_core-2.46.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66ce7632c22d837c95301830e111ad0128a32b8207533b60896a96c4915192ea", size = 2138417, upload-time = "2026-05-06T13:39:45.476Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1d/8987ad40f65ae1432753072f214fb5c74fe47ffbd0698bb9cbbb585664f8/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:1d8ba486450b14f3b1d63bc521d410ec7565e52f887b9fb671791886436a42f7", size = 2095527, upload-time = "2026-05-06T13:39:52.283Z" }, + { url = "https://files.pythonhosted.org/packages/64/d3/84c282a7eee1d3ac4c0377546ef5a1ea436ce26840d9ac3b7ed54a377507/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:3009f12e4e90b7f88b4f9adb1b0c4a3d58fe7820f3238c190047209d148026df", size = 1936024, upload-time = "2026-05-06T13:40:15.671Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ca/eac61596cdeb4d7e174d3dc0bd8a6238f14f75f97a24e7b7db4c7e7340a0/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad785e92e6dc634c21555edc8bd6b64957ab844541bcb96a1366c202951ae526", size = 1990696, upload-time = "2026-05-06T13:38:34.717Z" }, + { url = "https://files.pythonhosted.org/packages/fa/c3/7c8b240552251faf6b3a957db200fcfbbcec36763c050428b601e0c9b83b/pydantic_core-2.46.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00c603d540afdd6b80eb39f078f33ebd46211f02f33e34a32d9f053bba711de0", size = 2147590, upload-time = "2026-05-06T13:39:29.883Z" }, + { url = "https://files.pythonhosted.org/packages/11/cb/428de0385b6c8d44b716feba566abfacfbd23ee3c4439faa789a1456242f/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0c563b08bca408dc7f65f700633d8442fffb2421fc47b8101377e9fd65051ff0", size = 2112782, upload-time = "2026-05-06T13:37:04.016Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/6a17bdadd0fc1f170adfd05a20d37c832f52b117b4d9131da1f41bb097ce/pydantic_core-2.46.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:db06ffe51636ffe9ca531fe9023dd64bdd794be8754cb5df57c5498ae5b518a7", size = 1952146, upload-time = "2026-05-06T13:39:43.092Z" }, + { url = "https://files.pythonhosted.org/packages/2a/dc/03734d80e362cd43ef65428e9de77c730ce7f2f11c60d2b1e1b39f0fbf99/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133878133d271ade3d41d1bfb2a45ec38dbdbda40bc065921c6b04e4630127e2", size = 2134492, upload-time = "2026-05-06T13:36:58.124Z" }, + { url = "https://files.pythonhosted.org/packages/de/df/5e5ffc085ed07cc22d298134d3d911c63e91f6a0eb91fe646750a3209910/pydantic_core-2.46.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9bc519fbf2b7578398853d815009ae5e4d4603d12f4e3f91da8c06852d3da3e9", size = 2156604, upload-time = "2026-05-06T13:37:49.88Z" }, + { url = "https://files.pythonhosted.org/packages/81/44/6e112a4253e56f5705467cbab7ab5e91ee7398ba3d56d358635958893d3e/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:c7a7bd4e39e8e4c12c39cd480356842b6a8a06e41b23a55a5e3e191718838ddf", size = 2183828, upload-time = "2026-05-06T13:37:43.053Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/5565071e937d8e752842ac241463944c9eb14c87e2d269f2658a5bd05e98/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:d396ec2b979760aaf3218e76c24e65bd0aca24983298653b3a9d7a45f9e47b30", size = 2310000, upload-time = "2026-05-06T13:37:56.694Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c3/66883a5cec183e7fba4d024b4cbbe61851a63750ef606b0afecc46d1f2bf/pydantic_core-2.46.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:86e1a4418c6cd97d60c95c71164158eaf7324fae7b0923264016baa993eba6fc", size = 2361286, upload-time = "2026-05-06T13:40:05.667Z" }, + { url = "https://files.pythonhosted.org/packages/4b/2d/69abac8f838090bbecd5df894befb2c2619e7996a98ddb949db9f3b93225/pydantic_core-2.46.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:d51026d73fcfd93610abc7b27789c26b313920fcfb20e27462d74a7f8b06e983", size = 2193071, upload-time = "2026-05-06T13:38:08.682Z" }, +] + +[[package]] +name = "pygments" +version = "2.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "10.21.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "markdown", version = "3.9", source = { registry = "https://pypi.org/simple" } }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, +] + +[[package]] +name = "pymdown-extensions" +version = "11.0.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "markdown", version = "3.10.2", source = { registry = "https://pypi.org/simple" } }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/a9/5f0c535ba3b08fe09270c16808e053a968868242ecbd5676d4e3a488bf28/pymdown_extensions-11.0.1.tar.gz", hash = "sha256:dd2905ae6fc5b75582fafb139a1266ffc754705efa902aa50067fa7ff4f94ec0", size = 857113, upload-time = "2026-07-02T17:59:22.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d6/54/da572c98c0b77626a91b5d3b89f0231d8bff5125c225420908632f8b342d/pymdown_extensions-11.0.1-py3-none-any.whl", hash = "sha256:db3943a62bab7e03af1364f0c4083e64b91fb097675a4b6cceccfbe9a77e5eb2", size = 269455, upload-time = "2026-07-02T17:59:21.271Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.1.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" } }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-discovery" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f1/51/276f964496a5714ab9f320896195639086881c2b39c03b5ad13de84acbb8/python_discovery-1.5.0.tar.gz", hash = "sha256:3e014c6327154d3dda27939a9a0dc9c5c000439f1906d3f303b48f984bd2ecef", size = 72483, upload-time = "2026-07-21T13:14:14.641Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/7b/14882602ddee241d7984a742fcb423cb4a30fb0d6efc546ac3129fba475a/python_discovery-1.5.0-py3-none-any.whl", hash = "sha256:70c4fc61b4e7404e44f01d6fc44a715c4d685ca6cea83d295922f05891877c98", size = 34205, upload-time = "2026-07-21T13:14:13.398Z" }, +] + +[[package]] +name = "pytokens" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" }, + { url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" }, + { url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" }, + { url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" }, + { url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" }, + { url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" }, + { url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" }, + { url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" }, + { url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" }, + { url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" }, + { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" }, + { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" }, + { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" }, + { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" }, + { url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" }, + { url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" }, + { url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" }, + { url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" }, + { url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" }, + { url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/51/2a/f125667ce48105bf1f4e50e03cfa7b24b8c4f47684d7f1cf4dcb6f6b1c15/pytokens-0.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:34bcc734bd2f2d5fe3b34e7b3c0116bfb2397f2d9666139988e7a3eb5f7400e3", size = 161464, upload-time = "2026-01-30T01:03:39.11Z" }, + { url = "https://files.pythonhosted.org/packages/40/df/065a30790a7ca6bb48ad9018dd44668ed9135610ebf56a2a4cb8e513fd5c/pytokens-0.4.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:941d4343bf27b605e9213b26bfa1c4bf197c9c599a9627eb7305b0defcfe40c1", size = 246159, upload-time = "2026-01-30T01:03:40.131Z" }, + { url = "https://files.pythonhosted.org/packages/a5/1c/fd09976a7e04960dabc07ab0e0072c7813d566ec67d5490a4c600683c158/pytokens-0.4.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ad72b851e781478366288743198101e5eb34a414f1d5627cdd585ca3b25f1db", size = 259120, upload-time = "2026-01-30T01:03:41.233Z" }, + { url = "https://files.pythonhosted.org/packages/52/49/59fdc6fc5a390ae9f308eadeb97dfc70fc2d804ffc49dd39fc97604622ec/pytokens-0.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:682fa37ff4d8e95f7df6fe6fe6a431e8ed8e788023c6bcc0f0880a12eab80ad1", size = 262196, upload-time = "2026-01-30T01:03:42.696Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/d6734dccf0080e3dc00a55b0827ab5af30c886f8bc127bbc04bc3445daec/pytokens-0.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:30f51edd9bb7f85c748979384165601d028b84f7bd13fe14d3e065304093916a", size = 103510, upload-time = "2026-01-30T01:03:43.915Z" }, + { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, +] + +[[package]] +name = "pyyaml-env-tag" +version = "1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "2.6.3", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests" +version = "2.34.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3", version = "2.7.0", source = { registry = "https://pypi.org/simple" } }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856, upload-time = "2026-05-14T19:25:27.735Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075, upload-time = "2026-05-14T19:25:26.443Z" }, +] + +[[package]] +name = "rich" +version = "15.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "markdown-it-py", version = "4.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, +] + +[[package]] +name = "rss-parser" +version = "4.0.0" +source = { editable = "." } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, + { name = "xmltodict" }, +] + +[package.dev-dependencies] +dev = [ + { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "black", version = "26.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, + { name = "ipython", version = "9.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "mypy", version = "1.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "mypy", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pre-commit", version = "4.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pre-commit", version = "4.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "rich" }, + { name = "ruff" }, + { name = "types-xmltodict", version = "1.0.1.20260113", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "types-xmltodict", version = "1.0.1.20260518", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +docs = [ + { name = "mkdocs-material" }, +] + +[package.metadata] +requires-dist = [ + { name = "pydantic", specifier = ">=2.7,<3.0" }, + { name = "typing-extensions", specifier = ">=4.6" }, + { name = "xmltodict", specifier = ">=0.13.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "black" }, + { name = "ipython" }, + { name = "mypy" }, + { name = "pre-commit", specifier = ">=2.12.0" }, + { name = "pytest", specifier = ">=7.4.0" }, + { name = "rich" }, + { name = "ruff" }, + { name = "types-xmltodict", specifier = ">=0.14.0.20241009" }, +] +docs = [{ name = "mkdocs-material", specifier = ">=9.5" }] + +[[package]] +name = "ruff" +version = "0.15.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/06/ae069393fc66e8ff33036d4b368003833bf6e88ccf182e17e7a2f1c754fd/ruff-0.15.22.tar.gz", hash = "sha256:3f15175b1fb580126f58285a5dae6b2ea89000136d980c64499211f116b54809", size = 4785063, upload-time = "2026-07-16T15:14:13.244Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/18/ee54b7ae1e121be7a28ea6da4b67564ebb0530e183a54415ab7e3bcd2c4e/ruff-0.15.22-py3-none-linux_armv6l.whl", hash = "sha256:44423e73493737f5e7c5b41d475483898ff37afcdae38bc3da5085e29af1c2d8", size = 10781258, upload-time = "2026-07-16T15:13:19.452Z" }, + { url = "https://files.pythonhosted.org/packages/2f/d2/2520cb14761ddbeaf57642a76942fc36adcbdbe53b4532241995f6fc485c/ruff-0.15.22-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b82c6482946e9eda7ff2e091d25b8bad3f718684e1916d41bd56873cee05b697", size = 10999477, upload-time = "2026-07-16T15:13:23.318Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/74e53572aa758dfaa678c2a2646b5c5515d884b7ca56be4d2ce03ca4b560/ruff-0.15.22-py3-none-macosx_11_0_arm64.whl", hash = "sha256:11c1c715af53a09f714e011106bffc419751ec8232fcb5da42173284ea3fec6f", size = 10466716, upload-time = "2026-07-16T15:13:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/44eaaf0844e028182f2d0a8f2190d0f359159aed0a9e5ab861d892f1ae2a/ruff-0.15.22-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:742a29cf29bddb7c8327895d6a10e0e6c5b38a96dd407af9b5d0857f809c0576", size = 10892644, upload-time = "2026-07-16T15:13:29.229Z" }, + { url = "https://files.pythonhosted.org/packages/9f/21/8edf559014d2b0f82beea19cfb713993ad802ccda16868769979c6090a84/ruff-0.15.22-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72af58b951b0ae395935ae79763dc349bc0eb706319d28f7a33ad2cfb3cfc178", size = 10576719, upload-time = "2026-07-16T15:13:32.35Z" }, + { url = "https://files.pythonhosted.org/packages/bf/1e/3a13abd392a3b50b62e5938a831f9ab6e588358cacad5c18545b716d2182/ruff-0.15.22-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d425005c1835eb24e2ee4161cb90e8db263415f4a71c8c72c33abaa6c0c224", size = 11376494, upload-time = "2026-07-16T15:13:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/bf/3e/422d3d95bcf04dd78e1aeac22184d4f9a8fb2c01865d39d44618484a0317/ruff-0.15.22-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8b9b3f8779a4f08c969defc3c8c35abffaa757e601ed5ae66d6d1db6519969a", size = 12208370, upload-time = "2026-07-16T15:13:39.185Z" }, + { url = "https://files.pythonhosted.org/packages/1e/91/5d065a0e0a02bf4813f5119ad278462eed081d2b832eb7c021ade0ec9e65/ruff-0.15.22-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e0dd1b2e4d3d585f897a0d137cbf4eaf6223bef4e8ce34d6bb12556c5f9249e", size = 11581098, upload-time = "2026-07-16T15:13:42.132Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f9/a0d4871d12fae702eb1f41b686caf05f1f8b124dc6db6f784f53d74918fa/ruff-0.15.22-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365523eb91d9224e1bcb03b022fbf0facb8f9e23792a2c53d9d4b3924bdbdebb", size = 11399422, upload-time = "2026-07-16T15:13:45.2Z" }, + { url = "https://files.pythonhosted.org/packages/18/80/c843a5176cddbceb0b7e8dd41cf9993490796c1c469348d384f5a5c13c56/ruff-0.15.22-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:fabfd168afdf29fee5be98b831efa9683c94d7c5a3b58b9ce5a2e38444589a74", size = 11381683, upload-time = "2026-07-16T15:13:48.46Z" }, + { url = "https://files.pythonhosted.org/packages/d4/00/8485de0ae92239438a36cfc51350db9b9e85c9ebdfaea91b18e422706662/ruff-0.15.22-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:225dbf095a87f1d9f90f5fd7924d2613ee452a75a4308c63a8f50f761787aa7c", size = 10850295, upload-time = "2026-07-16T15:13:51.655Z" }, + { url = "https://files.pythonhosted.org/packages/fa/91/24977ec2ec72eaf15e4394ace2959fdff2dd1e14f03e005e838023407169/ruff-0.15.22-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1877d63b9d24ed278744f1523fd11b85540566d54641f97c566d7d9dc5ca5296", size = 10579640, upload-time = "2026-07-16T15:13:54.79Z" }, + { url = "https://files.pythonhosted.org/packages/9c/47/9b51216951974df1f263ac19da550d34252e0ed7218c25f10c5ef9ed7517/ruff-0.15.22-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a1606c510bd7215680d32efab38965f7cdec3ef69f5170a3f4791404ffdd5262", size = 11105077, upload-time = "2026-07-16T15:13:57.915Z" }, + { url = "https://files.pythonhosted.org/packages/c2/47/20e9d4a3b8016778acea5fc32bb50d35d207500a17ddb529ffa6996feef8/ruff-0.15.22-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:630479b18625f5ffc373f77603a22a9f8ac0acd7ff0501178b5db28ec71e9c64", size = 11490980, upload-time = "2026-07-16T15:14:01.032Z" }, + { url = "https://files.pythonhosted.org/packages/4d/76/3f72d8fc38c1cb77b38c56a70da9d0c17700cc1cc50f9649c9d3c8f5ba71/ruff-0.15.22-py3-none-win32.whl", hash = "sha256:e5ba0e4a13fd14abbed2a77b517a3911290c6c6c59ef67784328d1668fab76cf", size = 10789165, upload-time = "2026-07-16T15:14:04.16Z" }, + { url = "https://files.pythonhosted.org/packages/cb/46/4965251734c2b6fcdca1b1b187d20bcac3af0ee5b083b89c910bb961ce3a/ruff-0.15.22-py3-none-win_amd64.whl", hash = "sha256:9be63ba1eb936acd2d1342fb8337c356353706fce233b2a15a09a97037e6acde", size = 11938297, upload-time = "2026-07-16T15:14:07.316Z" }, + { url = "https://files.pythonhosted.org/packages/57/c9/e69b1ff4c8b69093ef08b8919ab767af0569666865b39c30a8795d88d3c6/ruff-0.15.22-py3-none-win_arm64.whl", hash = "sha256:e1168075b72158510839f250027659cdd78476f40507dd517892304c41318661", size = 11298172, upload-time = "2026-07-16T15:14:10.51Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asttokens" }, + { name = "executing" }, + { name = "pure-eval" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/e3/55dcc2cfbc3ca9c29519eb6884dd1415ecb53b0e934862d3559ddcb7e20b/stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9", size = 44707, upload-time = "2023-09-30T13:58:05.479Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/7b/ce1eafaf1a76852e2ec9b22edecf1daa58175c090266e9f6c64afcd81d91/stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695", size = 24521, upload-time = "2023-09-30T13:58:03.53Z" }, +] + +[[package]] +name = "tomli" +version = "2.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/22/de/48c59722572767841493b26183a0d1cc411d54fd759c5607c4590b6563a6/tomli-2.4.1.tar.gz", hash = "sha256:7c7e1a961a0b2f2472c1ac5b69affa0ae1132c39adcb67aba98568702b9cc23f", size = 17543, upload-time = "2026-03-25T20:22:03.828Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/11/db3d5885d8528263d8adc260bb2d28ebf1270b96e98f0e0268d32b8d9900/tomli-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f8f0fc26ec2cc2b965b7a3b87cd19c5c6b8c5e5f436b984e85f486d652285c30", size = 154704, upload-time = "2026-03-25T20:21:10.473Z" }, + { url = "https://files.pythonhosted.org/packages/6d/f7/675db52c7e46064a9aa928885a9b20f4124ecb9bc2e1ce74c9106648d202/tomli-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4ab97e64ccda8756376892c53a72bd1f964e519c77236368527f758fbc36a53a", size = 149454, upload-time = "2026-03-25T20:21:12.036Z" }, + { url = "https://files.pythonhosted.org/packages/61/71/81c50943cf953efa35bce7646caab3cf457a7d8c030b27cfb40d7235f9ee/tomli-2.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96481a5786729fd470164b47cdb3e0e58062a496f455ee41b4403be77cb5a076", size = 237561, upload-time = "2026-03-25T20:21:13.098Z" }, + { url = "https://files.pythonhosted.org/packages/48/c1/f41d9cb618acccca7df82aaf682f9b49013c9397212cb9f53219e3abac37/tomli-2.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a881ab208c0baf688221f8cecc5401bd291d67e38a1ac884d6736cbcd8247e9", size = 243824, upload-time = "2026-03-25T20:21:14.569Z" }, + { url = "https://files.pythonhosted.org/packages/22/e4/5a816ecdd1f8ca51fb756ef684b90f2780afc52fc67f987e3c61d800a46d/tomli-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47149d5bd38761ac8be13a84864bf0b7b70bc051806bc3669ab1cbc56216b23c", size = 242227, upload-time = "2026-03-25T20:21:15.712Z" }, + { url = "https://files.pythonhosted.org/packages/6b/49/2b2a0ef529aa6eec245d25f0c703e020a73955ad7edf73e7f54ddc608aa5/tomli-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ec9bfaf3ad2df51ace80688143a6a4ebc09a248f6ff781a9945e51937008fcbc", size = 247859, upload-time = "2026-03-25T20:21:17.001Z" }, + { url = "https://files.pythonhosted.org/packages/83/bd/6c1a630eaca337e1e78c5903104f831bda934c426f9231429396ce3c3467/tomli-2.4.1-cp311-cp311-win32.whl", hash = "sha256:ff2983983d34813c1aeb0fa89091e76c3a22889ee83ab27c5eeb45100560c049", size = 97204, upload-time = "2026-03-25T20:21:18.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/59/71461df1a885647e10b6bb7802d0b8e66480c61f3f43079e0dcd315b3954/tomli-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:5ee18d9ebdb417e384b58fe414e8d6af9f4e7a0ae761519fb50f721de398dd4e", size = 108084, upload-time = "2026-03-25T20:21:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/b8/83/dceca96142499c069475b790e7913b1044c1a4337e700751f48ed723f883/tomli-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:c2541745709bad0264b7d4705ad453b76ccd191e64aa6f0fc66b69a293a45ece", size = 95285, upload-time = "2026-03-25T20:21:20.309Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ba/42f134a3fe2b370f555f44b1d72feebb94debcab01676bf918d0cb70e9aa/tomli-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c742f741d58a28940ce01d58f0ab2ea3ced8b12402f162f4d534dfe18ba1cd6a", size = 155924, upload-time = "2026-03-25T20:21:21.626Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c7/62d7a17c26487ade21c5422b646110f2162f1fcc95980ef7f63e73c68f14/tomli-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f86fd587c4ed9dd76f318225e7d9b29cfc5a9d43de44e5754db8d1128487085", size = 150018, upload-time = "2026-03-25T20:21:23.002Z" }, + { url = "https://files.pythonhosted.org/packages/5c/05/79d13d7c15f13bdef410bdd49a6485b1c37d28968314eabee452c22a7fda/tomli-2.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff18e6a727ee0ab0388507b89d1bc6a22b138d1e2fa56d1ad494586d61d2eae9", size = 244948, upload-time = "2026-03-25T20:21:24.04Z" }, + { url = "https://files.pythonhosted.org/packages/10/90/d62ce007a1c80d0b2c93e02cab211224756240884751b94ca72df8a875ca/tomli-2.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:136443dbd7e1dee43c68ac2694fde36b2849865fa258d39bf822c10e8068eac5", size = 253341, upload-time = "2026-03-25T20:21:25.177Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/caf6496d60152ad4ed09282c1885cca4eea150bfd007da84aea07bcc0a3e/tomli-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e262d41726bc187e69af7825504c933b6794dc3fbd5945e41a79bb14c31f585", size = 248159, upload-time = "2026-03-25T20:21:26.364Z" }, + { url = "https://files.pythonhosted.org/packages/99/e7/c6f69c3120de34bbd882c6fba7975f3d7a746e9218e56ab46a1bc4b42552/tomli-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5cb41aa38891e073ee49d55fbc7839cfdb2bc0e600add13874d048c94aadddd1", size = 253290, upload-time = "2026-03-25T20:21:27.46Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/4a3c322f22c5c66c4b836ec58211641a4067364f5dcdd7b974b4c5da300c/tomli-2.4.1-cp312-cp312-win32.whl", hash = "sha256:da25dc3563bff5965356133435b757a795a17b17d01dbc0f42fb32447ddfd917", size = 98141, upload-time = "2026-03-25T20:21:28.492Z" }, + { url = "https://files.pythonhosted.org/packages/24/22/4daacd05391b92c55759d55eaee21e1dfaea86ce5c571f10083360adf534/tomli-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:52c8ef851d9a240f11a88c003eacb03c31fc1c9c4ec64a99a0f922b93874fda9", size = 108847, upload-time = "2026-03-25T20:21:29.386Z" }, + { url = "https://files.pythonhosted.org/packages/68/fd/70e768887666ddd9e9f5d85129e84910f2db2796f9096aa02b721a53098d/tomli-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:f758f1b9299d059cc3f6546ae2af89670cb1c4d48ea29c3cacc4fe7de3058257", size = 95088, upload-time = "2026-03-25T20:21:30.677Z" }, + { url = "https://files.pythonhosted.org/packages/07/06/b823a7e818c756d9a7123ba2cda7d07bc2dd32835648d1a7b7b7a05d848d/tomli-2.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:36d2bd2ad5fb9eaddba5226aa02c8ec3fa4f192631e347b3ed28186d43be6b54", size = 155866, upload-time = "2026-03-25T20:21:31.65Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/12645cf7f08e1a20c7eb8c297c6f11d31c1b50f316a7e7e1e1de6e2e7b7e/tomli-2.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eb0dc4e38e6a1fd579e5d50369aa2e10acfc9cace504579b2faabb478e76941a", size = 149887, upload-time = "2026-03-25T20:21:33.028Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e0/90637574e5e7212c09099c67ad349b04ec4d6020324539297b634a0192b0/tomli-2.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7f2c7f2b9ca6bdeef8f0fa897f8e05085923eb091721675170254cbc5b02897", size = 243704, upload-time = "2026-03-25T20:21:34.51Z" }, + { url = "https://files.pythonhosted.org/packages/10/8f/d3ddb16c5a4befdf31a23307f72828686ab2096f068eaf56631e136c1fdd/tomli-2.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f3c6818a1a86dd6dca7ddcaaf76947d5ba31aecc28cb1b67009a5877c9a64f3f", size = 251628, upload-time = "2026-03-25T20:21:36.012Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f1/dbeeb9116715abee2485bf0a12d07a8f31af94d71608c171c45f64c0469d/tomli-2.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d312ef37c91508b0ab2cee7da26ec0b3ed2f03ce12bd87a588d771ae15dcf82d", size = 247180, upload-time = "2026-03-25T20:21:37.136Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/16336ffd19ed4da28a70959f92f506233bd7cfc2332b20bdb01591e8b1d1/tomli-2.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51529d40e3ca50046d7606fa99ce3956a617f9b36380da3b7f0dd3dd28e68cb5", size = 251674, upload-time = "2026-03-25T20:21:38.298Z" }, + { url = "https://files.pythonhosted.org/packages/16/f9/229fa3434c590ddf6c0aa9af64d3af4b752540686cace29e6281e3458469/tomli-2.4.1-cp313-cp313-win32.whl", hash = "sha256:2190f2e9dd7508d2a90ded5ed369255980a1bcdd58e52f7fe24b8162bf9fedbd", size = 97976, upload-time = "2026-03-25T20:21:39.316Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/71dfd96bcc1c775420cb8befe7a9d35f2e5b1309798f009dca17b7708c1e/tomli-2.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d65a2fbf9d2f8352685bc1364177ee3923d6baf5e7f43ea4959d7d8bc326a36", size = 108755, upload-time = "2026-03-25T20:21:40.248Z" }, + { url = "https://files.pythonhosted.org/packages/83/7a/d34f422a021d62420b78f5c538e5b102f62bea616d1d75a13f0a88acb04a/tomli-2.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:4b605484e43cdc43f0954ddae319fb75f04cc10dd80d830540060ee7cd0243cd", size = 95265, upload-time = "2026-03-25T20:21:41.219Z" }, + { url = "https://files.pythonhosted.org/packages/3c/fb/9a5c8d27dbab540869f7c1f8eb0abb3244189ce780ba9cd73f3770662072/tomli-2.4.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:fd0409a3653af6c147209d267a0e4243f0ae46b011aa978b1080359fddc9b6cf", size = 155726, upload-time = "2026-03-25T20:21:42.23Z" }, + { url = "https://files.pythonhosted.org/packages/62/05/d2f816630cc771ad836af54f5001f47a6f611d2d39535364f148b6a92d6b/tomli-2.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a120733b01c45e9a0c34aeef92bf0cf1d56cfe81ed9d47d562f9ed591a9828ac", size = 149859, upload-time = "2026-03-25T20:21:43.386Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/66341bdb858ad9bd0ceab5a86f90eddab127cf8b046418009f2125630ecb/tomli-2.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:559db847dc486944896521f68d8190be1c9e719fced785720d2216fe7022b662", size = 244713, upload-time = "2026-03-25T20:21:44.474Z" }, + { url = "https://files.pythonhosted.org/packages/df/6d/c5fad00d82b3c7a3ab6189bd4b10e60466f22cfe8a08a9394185c8a8111c/tomli-2.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01f520d4f53ef97964a240a035ec2a869fe1a37dde002b57ebc4417a27ccd853", size = 252084, upload-time = "2026-03-25T20:21:45.62Z" }, + { url = "https://files.pythonhosted.org/packages/00/71/3a69e86f3eafe8c7a59d008d245888051005bd657760e96d5fbfb0b740c2/tomli-2.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7f94b27a62cfad8496c8d2513e1a222dd446f095fca8987fceef261225538a15", size = 247973, upload-time = "2026-03-25T20:21:46.937Z" }, + { url = "https://files.pythonhosted.org/packages/67/50/361e986652847fec4bd5e4a0208752fbe64689c603c7ae5ea7cb16b1c0ca/tomli-2.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ede3e6487c5ef5d28634ba3f31f989030ad6af71edfb0055cbbd14189ff240ba", size = 256223, upload-time = "2026-03-25T20:21:48.467Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9a/b4173689a9203472e5467217e0154b00e260621caa227b6fa01feab16998/tomli-2.4.1-cp314-cp314-win32.whl", hash = "sha256:3d48a93ee1c9b79c04bb38772ee1b64dcf18ff43085896ea460ca8dec96f35f6", size = 98973, upload-time = "2026-03-25T20:21:49.526Z" }, + { url = "https://files.pythonhosted.org/packages/14/58/640ac93bf230cd27d002462c9af0d837779f8773bc03dee06b5835208214/tomli-2.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:88dceee75c2c63af144e456745e10101eb67361050196b0b6af5d717254dddf7", size = 109082, upload-time = "2026-03-25T20:21:50.506Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2f/702d5e05b227401c1068f0d386d79a589bb12bf64c3d2c72ce0631e3bc49/tomli-2.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:b8c198f8c1805dc42708689ed6864951fd2494f924149d3e4bce7710f8eb5232", size = 96490, upload-time = "2026-03-25T20:21:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/45/4b/b877b05c8ba62927d9865dd980e34a755de541eb65fffba52b4cc495d4d2/tomli-2.4.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:d4d8fe59808a54658fcc0160ecfb1b30f9089906c50b23bcb4c69eddc19ec2b4", size = 164263, upload-time = "2026-03-25T20:21:52.543Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/6ab420d37a270b89f7195dec5448f79400d9e9c1826df982f3f8e97b24fd/tomli-2.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7008df2e7655c495dd12d2a4ad038ff878d4ca4b81fccaf82b714e07eae4402c", size = 160736, upload-time = "2026-03-25T20:21:53.674Z" }, + { url = "https://files.pythonhosted.org/packages/02/e0/3630057d8eb170310785723ed5adcdfb7d50cb7e6455f85ba8a3deed642b/tomli-2.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d8591993e228b0c930c4bb0db464bdad97b3289fb981255d6c9a41aedc84b2d", size = 270717, upload-time = "2026-03-25T20:21:55.129Z" }, + { url = "https://files.pythonhosted.org/packages/7a/b4/1613716072e544d1a7891f548d8f9ec6ce2faf42ca65acae01d76ea06bb0/tomli-2.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:734e20b57ba95624ecf1841e72b53f6e186355e216e5412de414e3c51e5e3c41", size = 278461, upload-time = "2026-03-25T20:21:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/05/38/30f541baf6a3f6df77b3df16b01ba319221389e2da59427e221ef417ac0c/tomli-2.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8a650c2dbafa08d42e51ba0b62740dae4ecb9338eefa093aa5c78ceb546fcd5c", size = 274855, upload-time = "2026-03-25T20:21:57.653Z" }, + { url = "https://files.pythonhosted.org/packages/77/a3/ec9dd4fd2c38e98de34223b995a3b34813e6bdadf86c75314c928350ed14/tomli-2.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:504aa796fe0569bb43171066009ead363de03675276d2d121ac1a4572397870f", size = 283144, upload-time = "2026-03-25T20:21:59.089Z" }, + { url = "https://files.pythonhosted.org/packages/ef/be/605a6261cac79fba2ec0c9827e986e00323a1945700969b8ee0b30d85453/tomli-2.4.1-cp314-cp314t-win32.whl", hash = "sha256:b1d22e6e9387bf4739fbe23bfa80e93f6b0373a7f1b96c6227c32bef95a4d7a8", size = 108683, upload-time = "2026-03-25T20:22:00.214Z" }, + { url = "https://files.pythonhosted.org/packages/12/64/da524626d3b9cc40c168a13da8335fe1c51be12c0a63685cc6db7308daae/tomli-2.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:2c1c351919aca02858f740c6d33adea0c5deea37f9ecca1cc1ef9e884a619d26", size = 121196, upload-time = "2026-03-25T20:22:01.169Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cd/e80b62269fc78fc36c9af5a6b89c835baa8af28ff5ad28c7028d60860320/tomli-2.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:eab21f45c7f66c13f2a9e0e1535309cee140182a9cdae1e041d02e47291e8396", size = 100393, upload-time = "2026-03-25T20:22:02.137Z" }, + { url = "https://files.pythonhosted.org/packages/7b/61/cceae43728b7de99d9b847560c262873a1f6c98202171fd5ed62640b494b/tomli-2.4.1-py3-none-any.whl", hash = "sha256:0d85819802132122da43cb86656f8d1f8c6587d54ae7dcaf30e90533028b49fe", size = 14583, upload-time = "2026-03-25T20:22:03.012Z" }, +] + +[[package]] +name = "traitlets" +version = "5.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/a9/a2584b8313b89f94869ddb3c4074617a691de1812a614d2d50e32ca5a7a6/traitlets-5.15.1.tar.gz", hash = "sha256:7b1c07854fe25acb39e009bae49f11b79ff6cbb2f27999104e9110e7a6b53722", size = 163344, upload-time = "2026-06-03T12:26:06.181Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/8d/1080ee4c231f361b6ce4470d556c8c435b67c7e0753aaa641497ee92f88b/traitlets-5.15.1-py3-none-any.whl", hash = "sha256:770a53705f84b81ac107e83a1b3328ff2dae16094d8fc3cfc004e4b22dfd8e92", size = 85858, upload-time = "2026-06-03T12:26:04.395Z" }, +] + +[[package]] +name = "types-xmltodict" +version = "1.0.1.20260113" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/32/da41b9af1da90eb0a46489c49375f74f29297ce0c4feb65a81f26faf1d41/types_xmltodict-1.0.1.20260113.tar.gz", hash = "sha256:d19ecffd2cf84956107432b0ef0d688dd606249dedfbcd16c66cec8357a95d97", size = 8752, upload-time = "2026-01-13T03:20:13.785Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/99/56f785dbd6ff16e0bed77bb5b2d151903e2c96ae370e2865d1da8a70982b/types_xmltodict-1.0.1.20260113-py3-none-any.whl", hash = "sha256:01a79b2f979aaf91d90982cdd878768ea9571866e7a2d50e82f9a4998e2d5d00", size = 8383, upload-time = "2026-01-13T03:20:12.744Z" }, +] + +[[package]] +name = "types-xmltodict" +version = "1.0.1.20260518" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/b8/5c43e147eec89125e3f56670c86bc6ba8ea4d0c4cb738a77b3f46940e50d/types_xmltodict-1.0.1.20260518.tar.gz", hash = "sha256:b9d0b364dc47508b4c9040635a94c28f5472f9ab588f6c864ae4c96823bccb33", size = 8890, upload-time = "2026-05-18T06:03:00.296Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/5d/e0eee00dd3c7299c1f5ae7ef596409c51dba4af93cfaf05ea7d27f4afc7d/types_xmltodict-1.0.1.20260518-py3-none-any.whl", hash = "sha256:b396dd53afbd8b034014922b95333f10d12515566a6a08282d39fdb1a3a529b9", size = 8377, upload-time = "2026-05-18T06:02:59.076Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/cc/6253133b5bb138fc3306cebfbda2c520f545d36b5be2c7255cc528bb45d6/typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5", size = 113555, upload-time = "2026-07-02T08:40:05.92Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8", size = 45571, upload-time = "2026-07-02T08:40:04.659Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "urllib3" +version = "2.7.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, +] + +[[package]] +name = "virtualenv" +version = "21.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock", version = "3.19.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "filelock", version = "3.32.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "platformdirs", version = "4.4.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "platformdirs", version = "4.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/25/e367a7229b0914772ca8d81b41fde012d9feda68523b52644a571bb21ce8/virtualenv-21.7.0.tar.gz", hash = "sha256:7f9519b9432ff11b6e1a3e94061664efc2ff99ea21780e3cf4f6bd0a5da8b37c", size = 5527510, upload-time = "2026-07-21T13:12:14.109Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/7a/ae29312b1e88a22e81f5d21fc11526d2a114089776c2550d2b205b6c2a47/virtualenv-21.7.0-py3-none-any.whl", hash = "sha256:a8370c1c5530fbabf955e40b8fbbc68a431648b10f9433faa587db30a06e51dd", size = 5507078, upload-time = "2026-07-21T13:12:12.136Z" }, +] + +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/56/90994d789c61df619bfc5ce2ecdabd5eeff564e1eb47512bd01b5e019569/watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26", size = 96390, upload-time = "2024-11-01T14:06:24.793Z" }, + { url = "https://files.pythonhosted.org/packages/55/46/9a67ee697342ddf3c6daa97e3a587a56d6c4052f881ed926a849fcf7371c/watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112", size = 88389, upload-time = "2024-11-01T14:06:27.112Z" }, + { url = "https://files.pythonhosted.org/packages/44/65/91b0985747c52064d8701e1075eb96f8c40a79df889e59a399453adfb882/watchdog-6.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c897ac1b55c5a1461e16dae288d22bb2e412ba9807df8397a635d88f671d36c3", size = 89020, upload-time = "2024-11-01T14:06:29.876Z" }, + { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, + { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, + { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, + { url = "https://files.pythonhosted.org/packages/05/52/7223011bb760fce8ddc53416beb65b83a3ea6d7d13738dde75eeb2c89679/watchdog-6.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e6f0e77c9417e7cd62af82529b10563db3423625c5fce018430b249bf977f9e8", size = 96390, upload-time = "2024-11-01T14:06:49.325Z" }, + { url = "https://files.pythonhosted.org/packages/9c/62/d2b21bc4e706d3a9d467561f487c2938cbd881c69f3808c43ac1ec242391/watchdog-6.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:90c8e78f3b94014f7aaae121e6b909674df5b46ec24d6bebc45c44c56729af2a", size = 88386, upload-time = "2024-11-01T14:06:50.536Z" }, + { url = "https://files.pythonhosted.org/packages/ea/22/1c90b20eda9f4132e4603a26296108728a8bfe9584b006bd05dd94548853/watchdog-6.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7631a77ffb1f7d2eefa4445ebbee491c720a5661ddf6df3498ebecae5ed375c", size = 89017, upload-time = "2024-11-01T14:06:51.717Z" }, + { url = "https://files.pythonhosted.org/packages/30/ad/d17b5d42e28a8b91f8ed01cb949da092827afb9995d4559fd448d0472763/watchdog-6.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c7ac31a19f4545dd92fc25d200694098f42c9a8e391bc00bdd362c5736dbf881", size = 87902, upload-time = "2024-11-01T14:06:53.119Z" }, + { url = "https://files.pythonhosted.org/packages/5c/ca/c3649991d140ff6ab67bfc85ab42b165ead119c9e12211e08089d763ece5/watchdog-6.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9513f27a1a582d9808cf21a07dae516f0fab1cf2d7683a742c498b93eedabb11", size = 88380, upload-time = "2024-11-01T14:06:55.19Z" }, + { url = "https://files.pythonhosted.org/packages/5b/79/69f2b0e8d3f2afd462029031baafb1b75d11bb62703f0e1022b2e54d49ee/watchdog-6.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7a0e56874cfbc4b9b05c60c8a1926fedf56324bb08cfbc188969777940aef3aa", size = 87903, upload-time = "2024-11-01T14:06:57.052Z" }, + { url = "https://files.pythonhosted.org/packages/e2/2b/dc048dd71c2e5f0f7ebc04dd7912981ec45793a03c0dc462438e0591ba5d/watchdog-6.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6439e374fc012255b4ec786ae3c4bc838cd7309a540e5fe0952d03687d8804e", size = 88381, upload-time = "2024-11-01T14:06:58.193Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + +[[package]] +name = "wcwidth" +version = "0.8.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/34/74/c6428f875774288bec1396f5bfcbc2d925700a4dad61727fd5f2b12f249d/wcwidth-0.8.2.tar.gz", hash = "sha256:91fbef97204b96a3d4d421609b80340b760cf33e26da123ff243d76b1fda8dda", size = 1466253, upload-time = "2026-06-29T18:11:11.601Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/42/3e5985a0a7e57de470b320c6d6a1a67c844f6737a587f3d44dd13d1819e7/wcwidth-0.8.2-py3-none-any.whl", hash = "sha256:d63947694a0539a1d51e01eda7caf800c291020e6cdd7e28ad7b14dd33ad4f85", size = 323166, upload-time = "2026-06-29T18:11:09.888Z" }, +] + +[[package]] +name = "xmltodict" +version = "1.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/70/80f3b7c10d2630aa66414bf23d210386700aa390547278c789afa994fd7e/xmltodict-1.0.4.tar.gz", hash = "sha256:6d94c9f834dd9e44514162799d344d815a3a4faec913717a9ecbfa5be1bb8e61", size = 26124, upload-time = "2026-02-22T02:21:22.074Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/34/98a2f52245f4d47be93b580dae5f9861ef58977d73a79eb47c58f1ad1f3a/xmltodict-1.0.4-py3-none-any.whl", hash = "sha256:a4a00d300b0e1c59fc2bfccb53d7b2e88c32f200df138a0dd2229f842497026a", size = 13580, upload-time = "2026-02-22T02:21:21.039Z" }, +] + +[[package]] +name = "zipp" +version = "3.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/30/21/093488dfc7cc8964ded15ab726fad40f25fd3d788fd741cc1c5a17d78ee8/zipp-3.23.1.tar.gz", hash = "sha256:32120e378d32cd9714ad503c1d024619063ec28aad2248dc6672ad13edfa5110", size = 25965, upload-time = "2026-04-13T23:21:46.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/8a/0861bec20485572fbddf3dfba2910e38fe249796cb73ecdeb74e07eeb8d3/zipp-3.23.1-py3-none-any.whl", hash = "sha256:0b3596c50a5c700c9cb40ba8d86d9f2cc4807e9bedb06bcdf7fac85633e444dc", size = 10378, upload-time = "2026-04-13T23:21:45.386Z" }, +] From 660ffda262af9ef0e6921af1c397ec8960f89e2d Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:05:22 +0200 Subject: [PATCH 13/20] chore: bump pre-commit-hooks to v6.0.0 --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 096bf03..cd6cefa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ repos: # Use pre-commit repo, v3.4.0 - repo: https://github.com/pre-commit/pre-commit-hooks - rev: "v3.4.0" + rev: "v6.0.0" hooks: # Check for files that contain merge conflict strings. - id: check-merge-conflict From 37212db2291450c36e66626b0eef614369f2e818 Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:26:24 +0200 Subject: [PATCH 14/20] fix!: actually flatten json_plain/dict_plain, make dumps round-trip Both found by the new test layers: - json_plain()/dict_plain() never flattened anything: pydantic's fallback= is only invoked for unserializable objects and Tag is a model, so the encoder never ran (broken the same way in 3.x). Reimplemented as a parallel walk of the model and its dump; flatten_tag_encoder is removed - Tag.pre_convert now recognizes the {content, attributes} dump shape, so model_validate(model_dump()) round-trips instead of double-wrapping --- rss_parser/models/__init__.py | 43 +++++++++++++++++++------ rss_parser/models/types/tag.py | 17 +++------- tests/test_serialization.py | 59 ++++++++++++++++++++++++++++++++++ 3 files changed, 98 insertions(+), 21 deletions(-) create mode 100644 tests/test_serialization.py diff --git a/rss_parser/models/__init__.py b/rss_parser/models/__init__.py index cde00f3..964cfba 100644 --- a/rss_parser/models/__init__.py +++ b/rss_parser/models/__init__.py @@ -1,10 +1,35 @@ from __future__ import annotations +import json +from typing import Any + from pydantic import BaseModel, ConfigDict from rss_parser.models.utils import camel_case +def _plainify(value: Any, dumped: Any) -> Any: + """Walk the model and its dump in parallel, flattening every Tag into its content.""" + from rss_parser.models.types.tag import Tag # noqa: PLC0415 + + if isinstance(value, Tag): + if isinstance(dumped, dict) and set(dumped) == {"content", "attributes"}: + dumped = dumped["content"] + return _plainify(value.content, dumped) + if isinstance(value, BaseModel): + result = {} + for name in type(value).model_fields: + if name in dumped: # respects include/exclude kwargs + result[name] = _plainify(getattr(value, name), dumped[name]) + for name in value.model_extra or {}: + if name in dumped: + result[name] = dumped[name] + return result + if isinstance(value, (list, tuple)): + return [_plainify(item, dumped_item) for item, dumped_item in zip(value, dumped)] + return dumped + + class XMLBaseModel(BaseModel): """ Base model for all XML-backed schemas. @@ -22,18 +47,18 @@ class XMLBaseModel(BaseModel): extra="allow", ) - def json_plain(self, **kwargs) -> str: + def dict_plain(self, **kwargs) -> dict: """ - Serialize the model while flattening Tag instances into their content. + Like ``model_dump(mode="json")``, but every Tag is flattened into its plain + content value, dropping the content/attributes structure. """ - from rss_parser.models.types.tag import Tag # noqa: PLC0415 + return _plainify(self, self.model_dump(mode="json", **kwargs)) - 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 json_plain(self, **kwargs) -> str: + """ + Serialize the model to JSON while flattening Tag instances into their content. + """ + return json.dumps(self.dict_plain(**kwargs), ensure_ascii=False) __all__ = ("XMLBaseModel",) diff --git a/rss_parser/models/types/tag.py b/rss_parser/models/types/tag.py index 4f2ab52..39eb853 100644 --- a/rss_parser/models/types/tag.py +++ b/rss_parser/models/types/tag.py @@ -4,9 +4,7 @@ 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 T = TypeVar("T") @@ -87,19 +85,14 @@ def pre_convert(cls, value: Union[T, dict, "Tag[T]"]) -> Union["Tag[T]", Dict[st return value if isinstance(value, dict): + if set(value) == {"content", "attributes"}: + # Already in Tag shape (e.g. re-validating a model_dump) - keep as is, + # so that model_validate(model_dump()) round-trips + return value + data = deepcopy(value) attributes = {snake_case(k.lstrip("@")): v for k, v in data.items() if k.startswith("@")} content = data.pop("#text", data) if len(attributes) != len(data) else None return {"content": content, "attributes": attributes} return {"content": value, "attributes": {}} - - @classmethod - def flatten_tag_encoder(cls, value): - """Encoder that translates Tag objects (dict) to plain .content values (T).""" - if isinstance(value, XMLBaseModel): - return value.dict_plain() - if isinstance(value, Tag): - return value.content - - return pydantic_encoder(value) diff --git a/tests/test_serialization.py b/tests/test_serialization.py new file mode 100644 index 0000000..cebe994 --- /dev/null +++ b/tests/test_serialization.py @@ -0,0 +1,59 @@ +import json + +from rss_parser import RSSParser + +XML = """ + + + Title + http://example.com + Description + 60 + + Item 1 + id-1 + Sat, 07 Sep 2002 00:00:01 GMT + + +""" + + +class TestModelDump: + def test_model_dump_keeps_tag_structure(self): + rss = RSSParser.parse(XML) + dump = rss.model_dump() + + assert dump["channel"]["content"]["title"]["content"] == "Title" + assert dump["channel"]["content"]["items"][0]["content"]["guid"]["attributes"] == {"is_perma_link": "false"} + + def test_model_dump_json_round_trips(self): + rss = RSSParser.parse(XML) + + assert json.loads(rss.model_dump_json())["version"]["content"] == "2.0" + + +class TestPlainSerialization: + def test_dict_plain_flattens_tags_to_content(self): + rss = RSSParser.parse(XML) + plain = rss.dict_plain() + + assert plain["version"] == "2.0" + assert plain["channel"]["title"] == "Title" + assert plain["channel"]["ttl"] == 60 + assert plain["channel"]["items"][0]["title"] == "Item 1" + + def test_dict_plain_serializes_datetimes(self): + plain = RSSParser.parse(XML).dict_plain() + + assert plain["channel"]["items"][0]["pub_date"] == "2002-09-07T00:00:01Z" + + def test_json_plain_matches_dict_plain(self): + rss = RSSParser.parse(XML) + + assert json.loads(rss.json_plain()) == rss.dict_plain() + + def test_unknown_tags_survive_serialization(self): + rss = RSSParser.parse(XML.replace("60", "kept")) + + assert rss.model_dump()["channel"]["content"]["custom:tag"] == "kept" + assert rss.dict_plain()["channel"]["custom:tag"] == "kept" From 4335b5fa19f01e19f97be3384f858e0e77bae906 Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:26:25 +0200 Subject: [PATCH 15/20] feat: raise InvalidXMLError for malformed XML Malformed input used to leak xmltodict's ExpatError. All parsers and detect_feed_type now raise InvalidXMLError (a ValueError subclass) with the original error chained as __cause__. --- rss_parser/__init__.py | 2 ++ rss_parser/_parser.py | 10 ++++++- tests/test_errors.py | 62 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 tests/test_errors.py diff --git a/rss_parser/__init__.py b/rss_parser/__init__.py index 464df68..4001368 100644 --- a/rss_parser/__init__.py +++ b/rss_parser/__init__.py @@ -3,6 +3,7 @@ AtomParser, BaseParser, FeedType, + InvalidXMLError, PodcastParser, RDFParser, RSSParser, @@ -16,6 +17,7 @@ "AtomParser", "BaseParser", "FeedType", + "InvalidXMLError", "PodcastParser", "RDFParser", "RSSParser", diff --git a/rss_parser/_parser.py b/rss_parser/_parser.py index 251cb9f..0f2071c 100644 --- a/rss_parser/_parser.py +++ b/rss_parser/_parser.py @@ -1,5 +1,6 @@ from enum import Enum from typing import Any, ClassVar, Dict, Mapping, Optional, Type +from xml.parsers.expat import ExpatError from xmltodict import parse as _xml_to_dict @@ -31,6 +32,10 @@ class UnknownFeedTypeError(ValueError): """Raised when the feed type cannot be detected from the XML root element.""" +class InvalidXMLError(ValueError): + """Raised when the data is not well-formed XML. The original ExpatError is available as __cause__.""" + + @abstract_class_attributes("schema") class BaseParser: """Parser for rss/atom/rdf files.""" @@ -40,7 +45,10 @@ class BaseParser: @staticmethod def to_xml(data: str, *args, **kwargs) -> Dict[str, Any]: - return _xml_to_dict(str(data), *args, **kwargs) + try: + return _xml_to_dict(str(data), *args, **kwargs) + except ExpatError as e: + raise InvalidXMLError(f"data is not well-formed XML: {e}") from e @classmethod def parse( diff --git a/tests/test_errors.py b/tests/test_errors.py new file mode 100644 index 0000000..0588232 --- /dev/null +++ b/tests/test_errors.py @@ -0,0 +1,62 @@ +from xml.parsers.expat import ExpatError + +import pytest +from pydantic import ValidationError + +from rss_parser import ( + AtomParser, + InvalidXMLError, + RSSParser, + UnknownFeedTypeError, + detect_feed_type, + parse, +) + + +class TestInvalidXML: + @pytest.mark.parametrize( + "data", + ["", "not xml at all", "unclosed", "", "\x00\x01"], + ids=["empty", "plain-text", "unclosed-tag", "mismatched-tag", "binary-junk"], + ) + def test_malformed_xml_raises_invalid_xml_error(self, data): + with pytest.raises(InvalidXMLError): + parse(data) + + def test_original_expat_error_is_chained(self): + with pytest.raises(InvalidXMLError) as exc_info: + RSSParser.parse("") + + assert isinstance(exc_info.value.__cause__, ExpatError) + + def test_detect_feed_type_raises_too(self): + with pytest.raises(InvalidXMLError): + detect_feed_type("garbage") + + def test_invalid_xml_error_is_a_value_error(self): + """Catching ValueError catches every rss-parser error.""" + assert issubclass(InvalidXMLError, ValueError) + assert issubclass(UnknownFeedTypeError, ValueError) + + +class TestUnknownFeedType: + def test_well_formed_non_feed_xml(self): + with pytest.raises(UnknownFeedTypeError, match="html"): + parse("hi") + + +class TestValidationErrors: + def test_error_path_points_at_the_broken_element(self): + with pytest.raises(ValidationError) as exc_info: + RSSParser.parse("TD") + + (error,) = exc_info.value.errors() + assert error["loc"] == ("channel", "content", "link") + assert error["type"] == "missing" + + def test_wrong_parser_for_feed_type_fails_with_validation_error(self): + with pytest.raises(ValidationError): + AtomParser.parse( + 'TL' + "D" + ) From eed42a13b6dff49c3fdc54d31360aaf322db115f Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:26:26 +0200 Subject: [PATCH 16/20] fix: make Atom updated optional - real-world feeds omit it The Atom spec requires feed/entry , but YouTube channel feeds have no feed-level at all. Spec purity loses to Google-scale reality; id and title stay required. --- rss_parser/models/atom/entry.py | 4 +- rss_parser/models/atom/feed.py | 4 +- tests/test_spec_atom.py | 102 ++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 tests/test_spec_atom.py diff --git a/rss_parser/models/atom/entry.py b/rss_parser/models/atom/entry.py index 53fc12a..0275d54 100644 --- a/rss_parser/models/atom/entry.py +++ b/rss_parser/models/atom/entry.py @@ -21,8 +21,8 @@ class Entry(XMLBaseModel): title: Tag[str] "The title of the entry." - updated: Tag[DateTimeOrStr] - "Indicates when the entry was updated." + updated: Optional[Tag[DateTimeOrStr]] = None + "Indicates when the entry was updated. Required by the spec, but omitted often enough in the wild that it's optional here." # noqa: E501 # Recommended entry elements diff --git a/rss_parser/models/atom/feed.py b/rss_parser/models/atom/feed.py index cfdc12a..7b1c262 100644 --- a/rss_parser/models/atom/feed.py +++ b/rss_parser/models/atom/feed.py @@ -29,8 +29,8 @@ class Feed(XMLBaseModel, Generic[EntryT]): 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." + updated: Optional[Tag[DateTimeOrStr]] = None + "Indicates the last time the feed was modified in a significant way. Required by the spec, but omitted by major real-world publishers (e.g. YouTube), so it's optional here." # noqa: E501 # Recommended feed elements diff --git a/tests/test_spec_atom.py b/tests/test_spec_atom.py new file mode 100644 index 0000000..1b7128a --- /dev/null +++ b/tests/test_spec_atom.py @@ -0,0 +1,102 @@ +from datetime import datetime + +import pytest +from pydantic import ValidationError + +from rss_parser import AtomParser +from rss_parser.models.atom.entry import Entry +from rss_parser.models.atom.source import Source + +FEED_TEMPLATE = """ + + urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 + Example Feed + 2003-12-13T18:30:02Z + {extra} +""" + + +def parse_feed(extra: str = ""): + return AtomParser.parse(FEED_TEMPLATE.format(extra=extra)).feed.content + + +class TestFeedElements: + def test_required_elements(self): + feed = parse_feed() + + assert feed.id.content == "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6" + assert feed.title.content == "Example Feed" + assert isinstance(feed.updated.content, datetime) + + def test_feed_without_id_is_rejected(self): + with pytest.raises(ValidationError, match="id"): + AtomParser.parse('T') + + def test_feed_without_updated_is_accepted(self): + """The spec requires , but major publishers (YouTube) omit it.""" + feed = AtomParser.parse( + 'urn:xT' + ).feed.content + + assert feed.updated is None + + def test_multiple_authors_and_links(self): + feed = parse_feed( + "Annann@example.com" + "Bob" + '' + '' + ) + + assert [str(a.content.name) for a in feed.authors] == ["Ann", "Bob"] + assert feed.authors[0].content.email.content == "ann@example.com" + assert [link.attributes["href"] for link in feed.links] == [ + "http://example.com/feed", + "http://example.com", + ] + + def test_single_link_is_still_a_list(self): + feed = parse_feed('') + + assert len(feed.links) == 1 + + +class TestEntryElements: + def test_entry_fields(self): + feed = parse_feed( + "" + "urn:entry:1" + "Entry title" + "2003-12-13T18:30:02Z" + "2003-12-12T18:30:02Z" + "Some text." + '<p>Body</p>' + "" + ) + entry = feed.entries[0].content + + assert isinstance(entry, Entry) + assert entry.id.content == "urn:entry:1" + assert isinstance(entry.updated.content, datetime) + assert isinstance(entry.published.content, datetime) + assert entry.summary.content == "Some text." + assert entry.content.content == "

Body

" + assert entry.content.attributes == {"type": "html"} + + def test_entry_without_title_is_rejected(self): + with pytest.raises(ValidationError, match="title"): + parse_feed("urn:entry:1") + + def test_entry_source_is_a_model(self): + feed = parse_feed( + "" + "urn:entry:1" + "Copied entry" + "urn:feed:origOriginal feed" + "2003-12-13T18:30:02Z" + "" + ) + source = feed.entries[0].content.source.content + + assert isinstance(source, Source) + assert source.title.content == "Original feed" From 83fe2d4e776838f1ef8c69bf3f0d0ae0a666c74d Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:26:27 +0200 Subject: [PATCH 17/20] test: real-world corpus, property-based suite, coverage floor Layered test architecture (51 -> 112 tests): - tests/corpus: 11 feeds captured from the wild (BBC, NPR, heise, HN, xkcd, GitHub, The Verge, YouTube, Slashdot RDF in ISO-8859-1, Lex Fridman podcast). Expectations in expect.json are derived by inspecting the raw XML, never by running the parser, so the parser is checked against the documents rather than against itself - property-based tests (hypothesis): text round-trips, lists are always lists, unknown tags are never dropped, validate(dump()) is idempotent - date fallback tests (RFC 822, ISO 8601, junk kept as string) - coverage floor enforced in CI (98%, currently 98.6%) --- .github/workflows/ci.yml | 2 +- pyproject.toml | 6 + tests/corpus/README.md | 33 + tests/corpus/atom/cpython_releases/data.xml | 118 ++ .../corpus/atom/cpython_releases/expect.json | 6 + tests/corpus/atom/theverge/data.xml | 348 ++++ tests/corpus/atom/theverge/expect.json | 6 + tests/corpus/atom/xkcd_atom/data.xml | 2 + tests/corpus/atom/xkcd_atom/expect.json | 6 + tests/corpus/atom/youtube/data.xml | 430 +++++ tests/corpus/atom/youtube/expect.json | 6 + tests/corpus/podcast/lex_fridman/data.xml | 400 ++++ tests/corpus/podcast/lex_fridman/expect.json | 8 + tests/corpus/rdf/slashdot/data.xml | 401 ++++ tests/corpus/rdf/slashdot/expect.json | 7 + tests/corpus/rss/bbc_news/data.xml | 306 +++ tests/corpus/rss/bbc_news/expect.json | 7 + tests/corpus/rss/heise/data.xml | 1699 +++++++++++++++++ tests/corpus/rss/heise/expect.json | 7 + tests/corpus/rss/hnrss/data.xml | 111 ++ tests/corpus/rss/hnrss/expect.json | 7 + tests/corpus/rss/npr/data.xml | 107 ++ tests/corpus/rss/npr/expect.json | 7 + tests/corpus/rss/xkcd_rss/data.xml | 2 + tests/corpus/rss/xkcd_rss/expect.json | 7 + tests/test_corpus.py | 110 ++ tests/test_dates.py | 41 + tests/test_properties.py | 80 + tests/test_spec_compliance.py | 5 + uv.lock | 357 ++++ 30 files changed, 4631 insertions(+), 1 deletion(-) create mode 100644 tests/corpus/README.md create mode 100644 tests/corpus/atom/cpython_releases/data.xml create mode 100644 tests/corpus/atom/cpython_releases/expect.json create mode 100644 tests/corpus/atom/theverge/data.xml create mode 100644 tests/corpus/atom/theverge/expect.json create mode 100644 tests/corpus/atom/xkcd_atom/data.xml create mode 100644 tests/corpus/atom/xkcd_atom/expect.json create mode 100644 tests/corpus/atom/youtube/data.xml create mode 100644 tests/corpus/atom/youtube/expect.json create mode 100644 tests/corpus/podcast/lex_fridman/data.xml create mode 100644 tests/corpus/podcast/lex_fridman/expect.json create mode 100644 tests/corpus/rdf/slashdot/data.xml create mode 100644 tests/corpus/rdf/slashdot/expect.json create mode 100644 tests/corpus/rss/bbc_news/data.xml create mode 100644 tests/corpus/rss/bbc_news/expect.json create mode 100644 tests/corpus/rss/heise/data.xml create mode 100644 tests/corpus/rss/heise/expect.json create mode 100644 tests/corpus/rss/hnrss/data.xml create mode 100644 tests/corpus/rss/hnrss/expect.json create mode 100644 tests/corpus/rss/npr/data.xml create mode 100644 tests/corpus/rss/npr/expect.json create mode 100644 tests/corpus/rss/xkcd_rss/data.xml create mode 100644 tests/corpus/rss/xkcd_rss/expect.json create mode 100644 tests/test_corpus.py create mode 100644 tests/test_dates.py create mode 100644 tests/test_properties.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d08188..18c8275 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,4 +45,4 @@ jobs: run: uv run mypy rss_parser - name: Test code with pytest - run: uv run pytest --doctest-modules rss_parser tests + run: uv run pytest --doctest-modules rss_parser tests --cov=rss_parser diff --git a/pyproject.toml b/pyproject.toml index 8773de3..3054536 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,6 +53,8 @@ dev = [ "pytest>=7.4.0", "mypy", "types-xmltodict>=0.14.0.20241009", + "hypothesis>=6.141.1", + "pytest-cov>=7.1.0", ] docs = [ "mkdocs-material>=9.5", @@ -70,6 +72,10 @@ testpaths = ["tests"] log_cli = true log_level = "INFO" +[tool.coverage.report] +fail_under = 98 +show_missing = true + [tool.black] line-length = 120 diff --git a/tests/corpus/README.md b/tests/corpus/README.md new file mode 100644 index 0000000..237161c --- /dev/null +++ b/tests/corpus/README.md @@ -0,0 +1,33 @@ +# Real-world feed corpus + +Real feeds captured from the wild on 2026-07-23, committed as-is (bytes untouched) so the +suite runs offline and deterministically. The one exception: `podcast/lex_fridman` was +truncated to its first 5 items (the full feed is ~2MB); the closing tags were re-added by hand. + +Each feed dir contains: + +- `data.xml` — the raw captured feed +- `expect.json` — expectations **derived by inspecting the raw XML**, not by running the + parser. This is what keeps the corpus honest: the parser is checked against the document, + not against itself. + +`expect.json` fields: `type` (detected feed type), `version` (RSS only), `title`, +`items` (count), `first_item_title`, optional `encoding` (when not utf-8, e.g. slashdot is +iso-8859-1) and per-feed spot facts (e.g. `itunes_author`). + +| Feed | Source | Notes | +| --- | --- | --- | +| rss/bbc_news | https://feeds.bbci.co.uk/news/rss.xml | media:\*, dc:\* namespaces | +| rss/heise | https://www.heise.de/rss/heise.rdf | RSS 2.0 despite the .rdf URL; German umlauts; 153 items | +| rss/hnrss | https://hnrss.org/frontpage | dc:creator, comments | +| rss/npr | https://feeds.npr.org/1001/rss.xml | npr:\*, content:encoded | +| rss/xkcd_rss | https://xkcd.com/rss.xml | minimal feed | +| atom/cpython_releases | https://github.com/python/cpython/releases.atom | GitHub-flavored Atom | +| atom/theverge | https://www.theverge.com/rss/index.xml | Atom despite the /rss/ URL | +| atom/xkcd_atom | https://xkcd.com/atom.xml | minimal feed | +| atom/youtube | https://www.youtube.com/feeds/videos.xml?channel_id=UC_x5XG1OV2P6uZZ5FSM9Ttw | **violates Atom spec**: no feed-level ``; yt:\*, media:\* | +| rdf/slashdot | https://rss.slashdot.org/Slashdot/slashdotMain | real RSS 1.0 (RDF), ISO-8859-1 encoded | +| podcast/lex_fridman | https://lexfridman.com/feed/podcast/ | itunes:\* tags, CDATA, truncated to 5 items | + +To add a feed: create `corpus///data.xml`, write `expect.json` by reading the +XML yourself, done — the test discovers it automatically. diff --git a/tests/corpus/atom/cpython_releases/data.xml b/tests/corpus/atom/cpython_releases/data.xml new file mode 100644 index 0000000..097e429 --- /dev/null +++ b/tests/corpus/atom/cpython_releases/data.xml @@ -0,0 +1,118 @@ + + + tag:github.com,2008:https://github.com/python/cpython/releases + + + Release notes from cpython + 2026-07-18T07:57:43Z + + tag:github.com,2008:Repository/81598961/v3.15.0b4 + 2026-07-18T07:57:43Z + + v3.15.0b4 + <p>Python 3.15.0b4</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.15.0b3 + 2026-06-23T09:35:49Z + + v3.15.0b3 + <p>Python 3.15.0b3</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.14.6 + 2026-06-10T10:03:53Z + + v3.14.6 + <p>Python 3.14.6</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.13.14 + 2026-06-10T12:24:04Z + + v3.13.14 + <p>Python 3.13.14</p> + + Yhg1s + + + + + tag:github.com,2008:Repository/81598961/v3.15.0b2 + 2026-06-02T15:28:41Z + + v3.15.0b2 + <p>Python 3.15.0b2</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.14.5 + 2026-05-10T10:21:34Z + + v3.14.5 + <p>Python 3.14.5</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.15.0b1 + 2026-05-07T13:26:31Z + + v3.15.0b1 + <p>Python 3.15.0b1</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.14.5rc1 + 2026-05-04T15:31:40Z + + v3.14.5rc1 + <p>Python 3.14.5rc1</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.15.0a8 + 2026-04-07T11:24:04Z + + v3.15.0a8 + <p>Python 3.15.0a8</p> + + hugovk + + + + + tag:github.com,2008:Repository/81598961/v3.14.4 + 2026-04-07T13:13:20Z + + v3.14.4 + <p>Python 3.14.4</p> + + hugovk + + + + diff --git a/tests/corpus/atom/cpython_releases/expect.json b/tests/corpus/atom/cpython_releases/expect.json new file mode 100644 index 0000000..62ab1f6 --- /dev/null +++ b/tests/corpus/atom/cpython_releases/expect.json @@ -0,0 +1,6 @@ +{ + "type": "atom", + "title": "Release notes from cpython", + "items": 10, + "first_item_title": "v3.15.0b4" +} diff --git a/tests/corpus/atom/theverge/data.xml b/tests/corpus/atom/theverge/data.xml new file mode 100644 index 0000000..6fa8973 --- /dev/null +++ b/tests/corpus/atom/theverge/data.xml @@ -0,0 +1,348 @@ + + The Verge + The Verge is about technology and how it makes us feel. Founded in 2011, we offer our audience everything from breaking news to reviews to award-winning features and investigations, on our site, in video, and in podcasts. + + 2026-07-22T22:03:53+00:00 + + + https://www.theverge.com/rss/index.xml + + + https://platform.theverge.com/wp-content/uploads/sites/2/2025/01/verge-rss-large_80b47e.png?w=150&h=150&crop=1 + + + + Lauren Feiner + + + <![CDATA[Meta won’t have to face the next planned social media addiction trial]]> + + https://www.theverge.com/?p=969644 + 2026-07-22T18:03:53-04:00 + 2026-07-22T18:03:53-04:00 + + + + + + +Mark Zuckerberg wearing sunglasses leaving a court house in a black SUV. +
+
+ +

Less than a week before Meta's lawyers were set to return to a Los Angeles courtroom, the plaintiff accusing the platform of inflicting harm dropped the case. Brought by 15-year-old Florida plaintiff going by initials R.K.C., the case was set to be the second in a set of bellwether trials meant to test legal arguments that social media giants allegedly broke the law by creating features that hooked and harmed teens.

+

TikTok, Snap, and YouTube previously settled claims brought by R.K.C. for undisclosed amounts. "In light of the overall successful result of the litigation and his concerns about enduring a grueling weekslong trial, he has elect …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Andrew J. Hawkins + + + <![CDATA[Tesla’s revenues are bouncing back, but profits are still weak]]> + + https://www.theverge.com/?p=969311 + 2026-07-22T17:02:05-04:00 + 2026-07-22T16:17:46-04:00 + + + + + + +Tesla logo +
+ A Tesla Model 3 electric car is seen during the China International Supply Chain Expo (CISCE) in Beijing on July 16, 2025. (Photo by Jade GAO / AFP) (Photo by JADE GAO/AFP via Getty Images) | AFP via Getty Images
+ +

After a dismal two years of weakening demand, falling sales, and damage to its brand by Elon Musk's political activities, Tesla's road to recovery continues apace. On the heels of an impressive delivery report, the company released its earnings for the second quarter of 2026 - giving us the latest glimpse at the EV company that Musk has said he wants to transform into a leader of AI and robotics.

+

Despite that mission, Tesla remains a car company. And in the second quarter, it sold an impressive 480,126 vehicles, about a 25 percent increase compared to the second quarter of 2025. (For a direct-to-consumer company like Tesla, deliveries are …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Brad Bourque + + + <![CDATA[Price-hiked iPads are a little cheaper right now]]> + + https://www.theverge.com/?p=969003 + 2026-07-22T15:14:31-04:00 + 2026-07-22T15:14:31-04:00 + + + + + + + +
+ The iPad Pro M5 is one of the iPads discounted for the sale. | Image: The Verge
+ +

A number of Apple products got more expensive last month, so we’re happy to find deals wherever and whenever we can. If you’re searching for a high-end iPad, one of the more notable deals currently happening is on the 13-inch iPad Pro M5 with 512GB of storage. Normally $1,699, it’s $150 off at Amazon, costing $1,549.99. It’s $100 off at Best Buy. This is the most premium iPad that Apple sells, with impressive performance from its M5 processor, a gorgeous OLED screen, up to 60W of fast charging, and support for the Apple Pencil Pro and Magic Keyboard. Read our iPad Pro (2025) review.

+ +

There’s also a deep discount on some configurations of the 11-inch iPad Air M3. This model sports 1TB of storage, an optional 5G cellular connection, and it’s marked down to $749, a $500 discount from the suggested retail price. While the M3 chip is on the older side, you get 1TB of storage for just $40 more than the base M4 version with 128GB. The discount is available in all four colors: blue, purple, space gray, and starlight. Read our Apple iPad Air M3 review.

+

11-inch iPad Air M3

+
The last-gen 11-inch iPad Air comes with Apple’s M3 chip and GPU upgrades. It’s available in dark gray, blue, purple, and a “starlight” cream shade. Read our review.
+
A photo of an iPad Air on a table.
+

Where to Buy:

+
+ +

Other deals to consider

+ +
    +
  • Sony is selling refurbished Playstation 5 Slim consoles for $499, a $100 break from the usual price. They may have some minor cosmetic imperfections, but are tested and certified by Sony to perform just as well as a new console. These slightly older digital only models have the full 1TB of storage, as opposed to the newer 825GB models, and have a one year warranty in case there are any issues.
  • + + + +
  • Normally $99.99, a four pack of the second-generation Apple AirTags are on sale for $89.99 at Amazon, matching their lowest price ever. The updated version of Apple’s popular Bluetooth tracker boosts the speaker volume, making it easier to find your stuff, and have more precise tracking. They’re still easy to setup, with one-touch pairing on iOS devices, and will run for over a year on a single replaceable battery.
  • + + + +
  • Amazon has the Razer Viper V3 Pro wireless gaming mouse listed for $99.99, about $30 lower than its usual price. The symmetrical mouse features an 8K polling rate and 35K DPI sensor, specs that will let you customize it to your play style, and eight programmable buttons to put every action right where you need it. At just 54 grams, the Viper V3 Pro is impressively lightweight, yet it still manages to last 95 hours of battery life on a single charge. There are plenty of less expensive gaming mice on the market, but this one’s well-rounded.
  • +
+ ]]> +
+ +
+ + + + Stevie Bonifield + + + <![CDATA[iOS code could reportedly let Apple cut off apps when users miss iPhone payments]]> + + https://www.theverge.com/?p=969596 + 2026-07-22T15:13:21-04:00 + 2026-07-22T15:13:21-04:00 + + + + + + +Photo of new Siri icon on iPhone +
+
+ +

Code found in an iOS 27 beta would allow Apple to put a financed iPhone in "Restricted Mode" if it detects any missed payments, 9to5Mac reports. The finding follows a story from Bloomberg earlier this week claiming Apple is preparing to launch a new "Apple Upgrade" financing program for leasing new devices.

+

iPhones in "Restricted Mode" would reportedly lose access to nearly all apps, except for basics like the Phone app, Settings, and the App Store. It uses a new "App Managed Features" system that allows financing or partner apps to continually check if a financed device is in good standing.

+

According to 9to5Mac, the code also includes a …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Stevie Bonifield + + + <![CDATA[Apple is reportedly testing a MacBook Neo with more RAM]]> + + https://www.theverge.com/?p=969434 + 2026-07-22T12:35:58-04:00 + 2026-07-22T12:35:58-04:00 + + + + + + + +
+
+ +

Following the MacBook Neo's huge popularity so far, Apple is reportedly developing an updated version of its budget laptop with a new processor and more memory, and even has plans to refresh the model with new colors. Following a similar report from analyst Tim Culpan earlier this year, Bloomberg's Mark Gurman says the new Neo will run on an A19 Pro processor, which currently powers the iPhone 17 Pro / Pro Max and the iPhone Air, unlike the current laptop's A18 Pro that was originally in the iPhone 16 Pro. The chip upgrade also means the updated Neo may have more RAM, likely 12GB rather than the original 8GB.

+

However, ongoing component sho …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Dominic Preston + + + <![CDATA[Here’s what Samsung’s smart glasses actually look like]]> + + https://www.theverge.com/?p=969382 + 2026-07-22T13:09:44-04:00 + 2026-07-22T12:35:34-04:00 + + + + + + + +
+ With a camera on every pair, Google’s and Samsung’s AI glasses face the same privacy problems as Meta’s. | Photo: Dominic Preston / The Verge
+ +

Samsung has given us our first chance to check out its upcoming smart glasses in person, revealing two new designs and the first specs in the process, including an impressive 9-hour battery life. The glasses, developed in collaboration with Google and the eyewear brands Gentle Monster and Warby Parker, are due to launch this fall.

+

Google and Samsung have been teasing the new glasses for some time, and revealed the first two designs at I/O in May. Now there are two more frames: Both are unsurprisingly boxy, with a new set of squared off black glasses from Gentle Monster, and a more rounded, red-brown pair from Warby Parker. Jaein Choi, the e …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Brad Bourque + + + <![CDATA[How the Galaxy Z Fold 8 and Z Flip 8 phones compare]]> + + https://www.theverge.com/?p=968682 + 2026-07-22T13:57:41-04:00 + 2026-07-22T11:47:10-04:00 + + + + + + + +
+ Showing off the new Samsung Galaxy Z Fold 8. | Photo: David Imel / The Verge
+ +

Samsung's latest round of folding Galaxy Z phones and updated smartwatches were announced at its July 2026 Unpacked event and are set to launch on August 7th. The Galaxy Z Flip 8, Fold 8, and Fold 8 Ultra don't seem geared toward people who bought last year's model; aside from a few design updates and AI features, there's not a whole lot that's new - unless we're talking about the Z Fold 8.

+

The Z Fold 8 is the foldable that will probably get the most attention in the US this year, that is, unless Apple's rumored foldable arrives in the coming months. While its name makes it sound like the successor to last year's Z Fold 7, it's a new, short …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Sheena Vasani + + + <![CDATA[Preorders for Samsung’s new Z Fold and Flip 8 come with up to $350 in gift cards]]> + + https://www.theverge.com/?p=968716 + 2026-07-22T11:59:37-04:00 + 2026-07-22T11:42:22-04:00 + + + + + + +The Galaxy Z Fold 8, Fold 8 Ultra, and Flip 8 +
+
+ +

Samsung's newest foldables are here. At Galaxy Unpacked, the company announced the Galaxy Z Flip 8, Galaxy Z Fold 8, and Galaxy Z Fold 8 Ultra. All three devices will be available on August 7th, but you can preorder them now starting at $1,199.99, $1,899.99, and $2,099.99, respectively.

+

You probably noticed that Samsung's foldable lineup looks a little different this year. Rather than simply updating the Galaxy Z Fold 7, Samsung now has two book-style foldables: the productivity-focused Galaxy Z Fold 8 Ultra and the shorter, wider (when folded) Galaxy Z Fold 8 that's geared more toward entertainment. The Galaxy Z Flip 8, meanwhile, is a tou …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Andrew Liszewski + + + <![CDATA[Philips’ new smart toothbrush shows you where you didn’t properly brush]]> + + https://www.theverge.com/?p=969271 + 2026-07-22T11:14:31-04:00 + 2026-07-22T11:09:44-04:00 + + + + + + +A person holds the Philips Sonicare Next-Generation DiamondClean 9900 Prestige smart toothbrush with a glowing aura around it. +
+
+ +

The latest addition to Philips' Sonicare line of smart electric toothbrushes could take the guesswork out of your brushing routine. The Next-Generation DiamondClean 9900 Prestige uses a third-generation AI model to provide real-time feedback on how you're brushing through a glowing ring on its base, while a touchscreen highlights areas of your mouth that may need more attention when you think you're done.

+

The Sonicare Next-Generation DiamondClean 9900 Prestige is expected to launch in Europe and the US this fall, with a wider global rollout coming in 2027. Pricing hasn't been announced, but previous versions sell for $430, and you can expec …

+

Read the full story at The Verge.

+ ]]> +
+ +
+ + + + Tom Warren + + + <![CDATA[Microsoft is bringing original Xbox games to PC]]> + + https://www.theverge.com/?p=969117 + 2026-07-22T11:03:20-04:00 + 2026-07-22T11:00:00-04:00 + + + + + + + +
+
+ +

Microsoft is expanding its Xbox backward compatibility efforts today by bringing original Xbox games to PC. An early preview release will see four classic Xbox games available on PC today, as part of a bigger effort to bring more Xbox console games to PC in the future.

+

The first four games are Blinx: The Time Sweeper, Conker: Live and Reloaded, Crimson Skies: High Road to Revenge, and Fuzion Frenzy. You'll be able to purchase and download each of these games on PC, and they'll also be included with all Xbox Game Pass plans. If you already own a digital copy of these games, you can now play them on PC or handhelds like the Xbox Ally and Xbox …

+

Read the full story at The Verge.

+ ]]> +
+ +
+
diff --git a/tests/corpus/atom/theverge/expect.json b/tests/corpus/atom/theverge/expect.json new file mode 100644 index 0000000..8b1ec23 --- /dev/null +++ b/tests/corpus/atom/theverge/expect.json @@ -0,0 +1,6 @@ +{ + "type": "atom", + "title": "The Verge", + "items": 10, + "first_item_title": "Meta won’t have to face the next planned social media addiction trial" +} diff --git a/tests/corpus/atom/xkcd_atom/data.xml b/tests/corpus/atom/xkcd_atom/data.xml new file mode 100644 index 0000000..b2eaf4d --- /dev/null +++ b/tests/corpus/atom/xkcd_atom/data.xml @@ -0,0 +1,2 @@ + +xkcd.comhttps://xkcd.com/2026-07-22T00:00:00ZCalibration Nobel2026-07-22T00:00:00Zhttps://xkcd.com/3275/<img src="https://imgs.xkcd.com/comics/calibration_nobel.png" title="We would like to once again apologize to Dr. Jones for last year's mistaken announcement. We should really have double-checked the envelope for this award in particular." alt="We would like to once again apologize to Dr. Jones for last year's mistaken announcement. We should really have double-checked the envelope for this award in particular." />Arthurian Connector2026-07-20T00:00:00Zhttps://xkcd.com/3274/<img src="https://imgs.xkcd.com/comics/arthurian_connector.png" title="Most coffee shops have a descendant of Sophia of Hanover on staff for this, but just as I was about to ask for help, a previously unknown heir of Uther Pendragon who was ordering a muffin tripped on my laptop cord." alt="Most coffee shops have a descendant of Sophia of Hanover on staff for this, but just as I was about to ask for help, a previously unknown heir of Uther Pendragon who was ordering a muffin tripped on my laptop cord." />Latitude and Longitude2026-07-17T00:00:00Zhttps://xkcd.com/3273/<img src="https://imgs.xkcd.com/comics/latitude_and_longitude.png" title="NGS and IERS are complaining that they left CLEAR instructions to set the washing machine to WGS84 (G2296) instead of WGS84 (G730)." alt="NGS and IERS are complaining that they left CLEAR instructions to set the washing machine to WGS84 (G2296) instead of WGS84 (G730)." />Time Change2026-07-15T00:00:00Zhttps://xkcd.com/3272/<img src="https://imgs.xkcd.com/comics/time_change.png" title="All discussions of daylight saving time policy are doomed by a mix of contradictory, inconsistent, and impossible preferences, which is why I think the only thing we can really hope to do is to make it worse." alt="All discussions of daylight saving time policy are doomed by a mix of contradictory, inconsistent, and impossible preferences, which is why I think the only thing we can really hope to do is to make it worse." /> \ No newline at end of file diff --git a/tests/corpus/atom/xkcd_atom/expect.json b/tests/corpus/atom/xkcd_atom/expect.json new file mode 100644 index 0000000..e8c0708 --- /dev/null +++ b/tests/corpus/atom/xkcd_atom/expect.json @@ -0,0 +1,6 @@ +{ + "type": "atom", + "title": "xkcd.com", + "items": 4, + "first_item_title": "Calibration Nobel" +} diff --git a/tests/corpus/atom/youtube/data.xml b/tests/corpus/atom/youtube/data.xml new file mode 100644 index 0000000..80a209a --- /dev/null +++ b/tests/corpus/atom/youtube/data.xml @@ -0,0 +1,430 @@ + + + + yt:channel:_x5XG1OV2P6uZZ5FSM9Ttw + _x5XG1OV2P6uZZ5FSM9Ttw + Google for Developers + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2007-08-23T00:34:43+00:00 + + yt:video:eiRlCVtzlm4 + eiRlCVtzlm4 + UC_x5XG1OV2P6uZZ5FSM9Ttw + Expose your site's actions to AI agents using WebMCP + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-22T23:00:37+00:00 + 2026-07-22T23:11:48+00:00 + + Expose your site's actions to AI agents using WebMCP + + + Instead of forcing agents to interpret your UI, use WebMCP to expose what it can actually do! + +Resources: +Learn more about WebMCP → https://goo.gle/4wdG9Af + +Subscribe to Google for Developers → https://goo.gle/developers + +Speaker: Sayali Godbole + + + + + + + + yt:video:JRArFxEfyQU + JRArFxEfyQU + UC_x5XG1OV2P6uZZ5FSM9Ttw + How are large language models trained? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-22T15:57:42+00:00 + 2026-07-22T16:11:44+00:00 + + How are large language models trained? + + + DeepMind’s Nikita Namjoshi breaks ​​down the two core phases of how large language models are trained: pre-training and post-training. Learn how next token prediction builds a model's foundation, and how supervised fine-tuning, reinforcement learning, and Autoraters refine it into a safe, helpful assistant. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speaker: Nikita Namjoshi +Products Mentioned: Google AI, Gemini + + + + + + + + yt:video:T_YzKVRlRsQ + T_YzKVRlRsQ + UC_x5XG1OV2P6uZZ5FSM9Ttw + Emojis in the username really do cause havoc, huh? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-22T04:00:29+00:00 + 2026-07-22T09:56:32+00:00 + + Emojis in the username really do cause havoc, huh? + + + So, your app works perfectly? Not for long when we introduce emojis in a username. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speakers: Justine Lai, Bri Davis + + + + + + + + yt:video:iK-JE0ZbCfg + iK-JE0ZbCfg + UC_x5XG1OV2P6uZZ5FSM9Ttw + Access all Gemini models with the Interactions API + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-20T23:00:10+00:00 + 2026-07-21T08:18:27+00:00 + + Access all Gemini models with the Interactions API + + + Say goodbye to complex AI workflows. The new Gemini Interactions API is a single-interface gateway that gives your AI agents stateful memory and multimodal powers. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speakers: Thor Schaeff +Products Mentioned: Gemini, Google AI + + + + + + + + yt:video:AaWdVp_py4Y + AaWdVp_py4Y + UC_x5XG1OV2P6uZZ5FSM9Ttw + What’s going wrong with this Kotlin code? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-20T13:00:13+00:00 + 2026-07-21T14:59:25+00:00 + + What’s going wrong with this Kotlin code? + + + Devs, this Kotlin challenge arises from a refactoring bug you might have seen. Here’s the setup: a teammate tidies up some code that builds job configurations. The build succeeds and everything looks fine. Then we check the list, and instead of the config object we expect, it holds something else. Watch the video and share what you think caused the bug! + +Subscribe to Google for Developers → https://goo.gle/developers + +Speakers: Anaya Mehta + + + + + + + + yt:video:iOK1-b_9Dlg + iOK1-b_9Dlg + UC_x5XG1OV2P6uZZ5FSM9Ttw + Get started with the Interactions API + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-17T23:00:34+00:00 + 2026-07-20T22:25:44+00:00 + + Get started with the Interactions API + + + The Interactions API has reached general availability and is now our primary API for interacting with Gemini models and agents. Whether you're calling a model or running an agent, the Interactions API gets you there in a few lines of code. + +Start building with the new API now → https://goo.gle/4fjN2Jg + +Subscribe to Google for Developers → https://goo.gle/developers + +Speaker: Thor Schaeff +Products Mentioned: Google AI, Gemini + + + + + + + + yt:video:8I7wr2hYFec + 8I7wr2hYFec + UC_x5XG1OV2P6uZZ5FSM9Ttw + Antigravity Arcade: From prompt to game in minutes + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-17T16:00:10+00:00 + 2026-07-17T16:11:19+00:00 + + Antigravity Arcade: From prompt to game in minutes + + + Explore how Antigravity and AI skills can generate web games in minutes with best practices skills and deployment workflows to an online games portal powered by Firebase and Google Cloud. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speaker: Tom Greenaway +Products Mentioned: Google AI + + + + + + + + yt:video:mgoNUC-tCAo + mgoNUC-tCAo + UC_x5XG1OV2P6uZZ5FSM9Ttw + Wait, what are we launching? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-17T04:00:14+00:00 + 2026-07-17T21:45:53+00:00 + + Wait, what are we launching? + + + For a launch, it’s all about rolling with the quick turn around times. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speakers: Chase Akumiah, Serena Cheng, Kadija Nabe + + + + + + + + yt:video:wjy2q43TsTk + wjy2q43TsTk + UC_x5XG1OV2P6uZZ5FSM9Ttw + What are the new features in Android Studio Quail 2? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-16T16:00:08+00:00 + 2026-07-21T13:15:13+00:00 + + What are the new features in Android Studio Quail 2? + + + It’s time to remove the manual friction from the debugging cycle. The latest updates in Android Studio Quail 2 are designed to bring essential development and troubleshooting tasks directly into a single, unified workspace. + +Subscribe to Google for Developers → https://goo.gle/developers + +#GoogleDeveloperNews + +Speakers: Sayali Godbole +Products Mentioned: Android + + + + + + + + yt:video:Q1TfKyOcosk + Q1TfKyOcosk + UC_x5XG1OV2P6uZZ5FSM9Ttw + The harsh reality of coding 💻 + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-15T04:00:40+00:00 + 2026-07-16T08:19:15+00:00 + + The harsh reality of coding 💻 + + + My code: "I did exactly what you asked!" + +Me: 🤦‍♂️ + +Subscribe to Google for Developers → https://goo.gle/developers + + + + + + + + yt:video:Q3ryjp64wK8 + Q3ryjp64wK8 + UC_x5XG1OV2P6uZZ5FSM9Ttw + What’s going wrong with this Kotlin code? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-13T13:00:25+00:00 + 2026-07-15T14:05:29+00:00 + + What’s going wrong with this Kotlin code? + + + Here’s a Kotlin focused developer challenge: A system is supposed to catch duplicate requests by comparing IDs. Two IDs are clearly the same. The logs confirm it. The values match. +However, the duplicate check fails, and the same request goes through twice. Watch the clip and share what you think is going on in the comments. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speakers: Anaya Mehta + + + + + + + + yt:video:8ZCeXQyavog + 8ZCeXQyavog + UC_x5XG1OV2P6uZZ5FSM9Ttw + Whats your go-to prompting hack? + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-10T19:00:32+00:00 + 2026-07-14T02:02:31+00:00 + + Whats your go-to prompting hack? + + + Here are some prompting tips that can save you time and yield a better product. + +Resources: +Learn more → https://goo.gle/Build-With-AI + +Subscribe to Google for Developers → https://goo.gle/developers + + + + + + + + yt:video:yGHi5V4sVkc + yGHi5V4sVkc + UC_x5XG1OV2P6uZZ5FSM9Ttw + Build With AI 2026 Recap + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-10T16:22:10+00:00 + 2026-07-22T18:52:19+00:00 + + Build With AI 2026 Recap + + + Watch our Build with AI 2026 highlights and see how AI architecture brings together all types of developers. + +Subscribe to Google for Developers → https://goo.gle/developers + +Products Mentioned: Google AI + + + + + + + + yt:video:ObomOeoP47Y + ObomOeoP47Y + UC_x5XG1OV2P6uZZ5FSM9Ttw + How to vibe code Android apps + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-09T16:13:57+00:00 + 2026-07-12T22:33:31+00:00 + + How to vibe code Android apps + + + Want to speed up your prototyping? Here is how to use AI Studio to build and deploy a native Android app using just prompts. + +Subscribe to Google for Developers → https://goo.gle/developers + + + + + + + + yt:video:Q4l3D0fI5dY + Q4l3D0fI5dY + UC_x5XG1OV2P6uZZ5FSM9Ttw + Create clean LLM service interfaces with wrapper classes! + + + Google for Developers + https://www.youtube.com/channel/UC_x5XG1OV2P6uZZ5FSM9Ttw + + 2026-07-08T23:00:31+00:00 + 2026-07-13T12:46:37+00:00 + + Create clean LLM service interfaces with wrapper classes! + + + When you ask an agent to update a feature, it has to search your entire codebase, which can limit the effectiveness of your AI coding agents. The fix? Separation of concerns. + +Subscribe to Google for Developers → https://goo.gle/developers + +Speaker: Christy Yao + + + + + + + diff --git a/tests/corpus/atom/youtube/expect.json b/tests/corpus/atom/youtube/expect.json new file mode 100644 index 0000000..f83ae29 --- /dev/null +++ b/tests/corpus/atom/youtube/expect.json @@ -0,0 +1,6 @@ +{ + "type": "atom", + "title": "Google for Developers", + "items": 15, + "first_item_title": "Expose your site's actions to AI agents using WebMCP" +} diff --git a/tests/corpus/podcast/lex_fridman/data.xml b/tests/corpus/podcast/lex_fridman/data.xml new file mode 100644 index 0000000..b6ca070 --- /dev/null +++ b/tests/corpus/podcast/lex_fridman/data.xml @@ -0,0 +1,400 @@ + + + + Lex Fridman Podcast + + https://lexfridman.com/ + Conversations that explore technology, history, philosophy, physics, mathematics, biology, chemistry, engineering, AI, robotics, programming, music, film, art, sports, psychology, neuroscience, geopolitics, business, economics, religion, astronomy, and the human condition with people from all walks of life. + Tue, 30 Jun 2026 21:33:40 +0000 + en-US + hourly + 1 + Blubrry PowerPress/11.16.10 + + https://lexfridman.com/feed/podcast/ + Lex Fridman + false + + + Lex Fridman + lexfridman@gmail.com + + podcast + + Lex Fridman Podcast + https://lexfridman.com/wordpress/wp-content/uploads/powerpress/artwork_3000-230.png + https://lexfridman.com/podcast + + + + + + + + 7eeae9d1-141e-5133-9e8f-6c1da695e40c + + + #498 – Anthony Kaldellis: Roman Empire, Byzantine Empire, Rise & Fall of Empires + https://lexfridman.com/anthony-kaldellis/?utm_source=rss&utm_medium=rss&utm_campaign=anthony-kaldellis + Tue, 30 Jun 2026 21:33:40 +0000 + https://lexfridman.com/?p=6466 + https://lexfridman.com/anthony-kaldellis/#respond + https://lexfridman.com/anthony-kaldellis/feed/ + 0 + + Anthony Kaldellis is a historian of the Roman Empire and author of “The New Roman Empire”, a comprehensive history of the Byzantine Empire (Eastern Roman Empire).
+Thank you for listening ❤ Check out our sponsors: https://lexfridman.com/sponsors/ep498-sc
+See below for timestamps, transcript, and to give feedback, submit questions, contact Lex, etc.

+

Transcript:
+https://lexfridman.com/anthony-kaldellis-transcript

+

CONTACT LEX:
+Feedback – give feedback to Lex: https://lexfridman.com/survey
+AMA – submit questions, videos or call-in: https://lexfridman.com/ama
+Hiring – join our team: https://lexfridman.com/hiring
+Other – other ways to get in touch: https://lexfridman.com/contact

+

EPISODE LINKS:
+Anthony’s Books: https://amzn.to/49AX7Q1
+Anthony’s Publications: https://kaldellispublications.weebly.com
+Anthony’s University of Chicago page: https://classics.uchicago.edu/people/anthony-kaldellis
+The New Roman Empire (book): https://amzn.to/3PTFTqk
+Streams of Gold (book): https://amzn.to/4fgRMRq
+Byzantium & Friends Podcast: https://byzantiumandfriends.podbean.com/
+The History of Byzantium Podcast: https://thehistoryofbyzantium.com/

+

SPONSORS:
+To support this podcast, check out our sponsors & get discounts:
+Upwork: Platform for hiring freelancers.
+Go to https://upwork.com/lex
+Fin: AI agent for customer service.
+Go to https://fin.ai/lex
+BetterHelp: Online therapy and counseling.
+Go to https://betterhelp.com/lex
+LMNT: Zero-sugar electrolyte drink mix.
+Go to https://drinkLMNT.com/lex
+Shopify: Sell stuff online.
+Go to https://shopify.com/lex
+Perplexity: AI-powered answer engine.
+Go to https://perplexity.ai/

+

OUTLINE:
+(00:00) – Introduction
+(00:11) – Sponsors, Comments, and Reflections
+(08:45) – The Roman Empire and the Byzantine Empire
+(12:42) – 2,200 Years of Roman History
+(33:06) – Power, violence, and civil war
+(54:20) – Edict of Caracalla
+(1:07:17) – Crisis of the Third Century
+(1:21:45) – Constantine and the new Roman Empire
+(1:33:46) – Christianity in the Roman Empire
+(1:59:14) – Fall of the Western Roman Empire
+(2:12:11) – Eunuchs, Taxes, and Power
+(2:37:17) – Emperor Justinian and wars of conquest
+(2:54:19) – The Arab conquests
+(3:13:55) – Why the Roman empire survived so long
+(3:40:01) – Lessons from history

+

PODCAST LINKS:
+– Podcast Website: https://lexfridman.com/podcast
+– Apple Podcasts: https://apple.co/2lwqZIr
+– Spotify: https://spoti.fi/2nEwCF8
+– RSS: https://lexfridman.com/feed/podcast/
+– Podcast Playlist: https://www.youtube.com/playlist?list=PLrAXtmErZgOdP_8GztsuKi9nrraNbKKp4
+– Clips Channel: https://www.youtube.com/lexclips

]]>
+ + full + 154171889 +
+ + #497 – Biggest Mysteries in Physics: Antimatter, Dark Energy & ToE – Don Lincoln + https://lexfridman.com/don-lincoln/?utm_source=rss&utm_medium=rss&utm_campaign=don-lincoln + Fri, 29 May 2026 16:22:02 +0000 + https://lexfridman.com/?p=6457 + https://lexfridman.com/don-lincoln/#respond + https://lexfridman.com/don-lincoln/feed/ + 0 + + Don Lincoln is a particle physicist at Fermilab who has spent decades working at the frontiers of high energy physics.
+Thank you for listening ❤ Check out our sponsors: https://lexfridman.com/sponsors/ep497-sc
+See below for timestamps, and to give feedback, submit questions, contact Lex, etc.

+

CONTACT LEX:
+Feedback – give feedback to Lex: https://lexfridman.com/survey
+AMA – submit questions, videos or call-in: https://lexfridman.com/ama
+Hiring – join our team: https://lexfridman.com/hiring
+Other – other ways to get in touch: https://lexfridman.com/contact

+

EPISODE LINKS:
+Don’s Facebook: https://facebook.com/Dr.Don.Lincoln/
+Don’s Website: https://drdonlincoln.com/
+Don’s LinkedIn: https://bit.ly/4nHeNiF
+Don’s YouTube Playlist: https://bit.ly/3PCIW67
+Don’s X: https://x.com/DrDonLincoln
+Don’s Books: https://amzn.to/4uYbkOZ
+Don’s Great Courses: https://shop.thegreatcourses.com/don-lincoln
+Don’s Audible: https://adbl.co/4wGioRV
+Fermilab’s YouTube: https://www.youtube.com/fermilab
+Fermilab’s Website: https://www.fnal.gov/
+Fermilab’s X: https://x.com/fermilab

+

SPONSORS:
+To support this podcast, check out our sponsors & get discounts:
+Upwork: Platform for hiring freelancers.
+Go to https://upwork.com/lex
+Larridin: Measure AI adoption in your business.
+Go to https://larridin.com
+Fin: AI agent for customer service.
+Go to https://fin.ai/lex
+LMNT: Zero-sugar electrolyte drink mix.
+Go to https://drinkLMNT.com/lex
+Shopify: Sell stuff online.
+Go to https://shopify.com/lex
+Perplexity: AI-powered answer engine.
+Go to https://perplexity.ai/

+

OUTLINE:
+(00:00) – Introduction
+(00:34) – Sponsors, Comments, and Reflections
+(08:52) – Unifying the laws of nature
+(23:23) – Einstein, special relativity, and general relativity
+(40:31) – Electroweak force
+(52:13) – How particle colliders work
+(1:10:16) – Higgs boson discovery
+(1:20:35) – Theory of everything
+(1:50:20) – Physics of empty space
+(1:57:45) – Antimatter
+(2:18:35) – Dark energy
+(2:22:23) – Dark matter
+(2:50:59) – Future of physics

+

PODCAST LINKS:
+– Podcast Website: https://lexfridman.com/podcast
+– Apple Podcasts: https://apple.co/2lwqZIr
+– Spotify: https://spoti.fi/2nEwCF8
+– RSS: https://lexfridman.com/feed/podcast/
+– Podcast Playlist: https://www.youtube.com/playlist?list=PLrAXtmErZgOdP_8GztsuKi9nrraNbKKp4
+– Clips Channel: https://www.youtube.com/lexclips

]]>
+ + full + 3:01:52 + 153917424 +
+ + #496 – FFmpeg: The Incredible Technology Behind Video on the Internet + https://lexfridman.com/ffmpeg/?utm_source=rss&utm_medium=rss&utm_campaign=ffmpeg + Wed, 06 May 2026 22:06:47 +0000 + https://lexfridman.com/?p=6450 + https://lexfridman.com/ffmpeg/#respond + https://lexfridman.com/ffmpeg/feed/ + 0 + + Jean-Baptiste Kempf is lead developer of VLC and president of VideoLAN. Kieran Kunhya is a longtime FFmpeg contributor, codec engineer, and the person behind the now-infamous FFmpeg account on X.
+Thank you for listening ❤ Check out our sponsors: https://lexfridman.com/sponsors/ep496-sc
+See below for timestamps, transcript, and to give feedback, submit questions, contact Lex, etc.

+

Transcript:
+https://lexfridman.com/ffmpeg-transcript

+

CONTACT LEX:
+Feedback – give feedback to Lex: https://lexfridman.com/survey
+AMA – submit questions, videos or call-in: https://lexfridman.com/ama
+Hiring – join our team: https://lexfridman.com/hiring
+Other – other ways to get in touch: https://lexfridman.com/contact

+

EPISODE LINKS:
+FFmpeg on X: https://x.com/FFmpeg
+FFmpeg: https://ffmpeg.org/
+VideoLAN (VLC): https://www.videolan.org/
+VideoLAN on X: https://x.com/videolan
+Jean-Baptiste’s Website: https://jbkempf.com/
+Jean-Baptiste’s LinkedIn: https://www.linkedin.com/in/jbkempf/
+Jean-Baptiste’s GitHub: https://github.com/jbkempf
+Kieran’s X: https://x.com/kierank_
+Kieran’s LinkedIn: https://bit.ly/3OORhmC
+Kieran’s GitHub: https://github.com/kierank

+

SPONSORS:
+To support this podcast, check out our sponsors & get discounts:
+Larridin: Measure AI adoption in your business.
+Go to https://larridin.com
+Blitzy: AI agent for large enterprise codebases.
+Go to https://blitzy.com/lex
+BetterHelp: Online therapy and counseling.
+Go to https://betterhelp.com/lex
+Fin: AI agent for customer service.
+Go to https://fin.ai/lex
+LMNT: Zero-sugar electrolyte drink mix.
+Go to https://drinkLMNT.com/lex
+Perplexity: AI-powered answer engine.
+Go to https://perplexity.ai/

+

OUTLINE:
+(00:00) – Introduction
+(03:00) – Sponsors, Comments, and Reflections
+(10:48) – Weirdest things VLC opens
+(15:12) – How video playback works
+(24:33) – Video codecs and containers
+(35:20) – FFmpeg explained
+(56:20) – Linus Torvalds
+(1:00:59) – Turning down millions to keep VLC ad-free
+(1:15:17) – FFmpeg & Google drama
+(1:34:31) – FFmpeg developers
+(1:41:08) – VLC and FFmpeg
+(1:45:42) – History of FFmpeg
+(1:48:59) – Reverse engineering codecs
+(2:02:14) – FFmpeg testing
+(2:06:21) – Assembly code (handwritten)
+(2:30:39) – Rust programming language
+(2:39:55) – FFmpeg and Libav fork
+(2:48:17) – Open source burnout
+(2:56:04) – x264 and internet video
+(3:09:20) – Video compression basics
+(3:16:17) – CIA and fake VLC
+(3:26:52) – Ultra low latency streaming
+(3:44:20) – AV2 codec and video patents
+(3:54:12) – VLC backdoors
+(4:04:27) – Video archiving
+(4:11:04) – Future of FFmpeg and VLC

]]>
+ + full + 4:23:41 + 153865024 +
+ + #495 – Vikings, Ragnar, Berserkers, Valhalla & the Warriors of the Viking Age + https://lexfridman.com/lars-brownworth/?utm_source=rss&utm_medium=rss&utm_campaign=lars-brownworth + Thu, 09 Apr 2026 17:43:17 +0000 + https://lexfridman.com/?p=6441 + https://lexfridman.com/lars-brownworth/#respond + https://lexfridman.com/lars-brownworth/feed/ + 0 + + Lars Brownworth is a historian, teacher, podcaster, and author specializing in Viking history, medieval Europe, and the Byzantine Empire.
+Thank you for listening ❤ Check out our sponsors: https://lexfridman.com/sponsors/ep495-sc
+See below for timestamps, transcript, and to give feedback, submit questions, contact Lex, etc.

+

Transcript:
+https://lexfridman.com/lars-brownworth-transcript

+

CONTACT LEX:
+Feedback – give feedback to Lex: https://lexfridman.com/survey
+AMA – submit questions, videos or call-in: https://lexfridman.com/ama
+Hiring – join our team: https://lexfridman.com/hiring
+Other – other ways to get in touch: https://lexfridman.com/contact

+

EPISODE LINKS:
+Lars’s Website: https://larsbrownworth.com/
+The Sea Wolves (book): https://www.amazon.com/Sea-Wolves-History-Vikings/dp/1909979120
+Lars’s Books: https://amzn.to/4sHY0xw
+12 Byzantine Rulers Podcast : https://12byzantinerulers.com/
+Norman Centuries Podcast: https://apple.co/4sgSxNi

+

SPONSORS:
+To support this podcast, check out our sponsors & get discounts:
+Larridin: Measure AI adoption in your business.
+Go to https://larridin.com
+BetterHelp: Online therapy and counseling.
+Go to https://betterhelp.com/lex
+LMNT: Zero-sugar electrolyte drink mix.
+Go to https://drinkLMNT.com/lex
+Fin: AI agent for customer service.
+Go to https://fin.ai/lex
+Shopify: Sell stuff online.
+Go to https://shopify.com/lex
+Perplexity: AI-powered answer engine.
+Go to https://perplexity.ai/

+

OUTLINE:
+(00:00) – Introduction
+(01:03) – Sponsors, Comments, and Reflections
+(08:57) – The start of the Viking Age
+(18:50) – Viking military strategy, tactics & technology
+(32:33) – Ragnar Lothbrok
+(42:00) – The Great Heathen Army
+(46:42) – Rollo and Normandy
+(56:54) – Viking religion and Valhalla
+(1:07:25) – Viking explorers
+(1:12:33) – Vikings in North America
+(1:25:55) – Vikings in the East
+(1:45:33) – Byzantine Empire
+(1:54:17) – History and human nature

+

PODCAST LINKS:
+– Podcast Website: https://lexfridman.com/podcast
+– Apple Podcasts: https://apple.co/2lwqZIr
+– Spotify: https://spoti.fi/2nEwCF8
+– RSS: https://lexfridman.com/feed/podcast/
+– Podcast Playlist: https://www.youtube.com/playlist?list=PLrAXtmErZgOdP_8GztsuKi9nrraNbKKp4
+– Clips Channel: https://www.youtube.com/lexclips

]]>
+ + full + 2:09:56 + 153798039 +
+ + #494 – Jensen Huang: NVIDIA – The $4 Trillion Company & the AI Revolution + https://lexfridman.com/jensen-huang/?utm_source=rss&utm_medium=rss&utm_campaign=jensen-huang + Mon, 23 Mar 2026 16:28:42 +0000 + https://lexfridman.com/?p=6434 + https://lexfridman.com/jensen-huang/#respond + https://lexfridman.com/jensen-huang/feed/ + 0 + + Jensen Huang is the co-founder and CEO of NVIDIA, the world’s most valuable company and the engine powering the AI computing revolution.
+Thank you for listening ❤ Check out our sponsors: https://lexfridman.com/sponsors/ep494-sc
+See below for timestamps, transcript, and to give feedback, submit questions, contact Lex, etc.

+

Transcript:
+https://lexfridman.com/jensen-huang-transcript

+

CONTACT LEX:
+Feedback – give feedback to Lex: https://lexfridman.com/survey
+AMA – submit questions, videos or call-in: https://lexfridman.com/ama
+Hiring – join our team: https://lexfridman.com/hiring
+Other – other ways to get in touch: https://lexfridman.com/contact

+

EPISODE LINKS:
+NVIDIA: https://nvidia.com
+NVIDIA on X: https://x.com/nvidia
+NVIDIA AI on X: https://x.com/NVIDIAAI
+NVIDIA on YouTube: https://youtube.com/@nvidia
+NVIDIA on Instagram: https://www.instagram.com/nvidia/
+NVIDIA on LinkedIn: https://www.linkedin.com/company/nvidia/
+NVIDIA on Facebook: https://www.facebook.com/NVIDIA/
+NVIDIA on GitHub: https://github.com/NVIDIA
+Nemotron: https://developer.nvidia.com/nemotron

+

SPONSORS:
+To support this podcast, check out our sponsors & get discounts:
+Perplexity: AI-powered answer engine.
+Go to https://perplexity.ai/
+Shopify: Sell stuff online.
+Go to https://shopify.com/lex
+LMNT: Zero-sugar electrolyte drink mix.
+Go to https://drinkLMNT.com/lex
+Fin: AI agent for customer service.
+Go to https://fin.ai/lex
+Quo: Phone system (calls, texts, contacts) for businesses.
+Go to https://quo.com/lex

+

OUTLINE:
+(00:00) – Introduction
+(00:26) – Sponsors, Comments, and Reflections
+(06:34) – Extreme co-design and rack-scale engineering
+(09:20) – How Jensen runs NVIDIA
+(28:41) – AI scaling laws
+(43:41) – Biggest blockers to AI scaling laws
+(45:25) – Supply chain
+(47:20) – Memory
+(53:25) – Power
+(58:45) – Elon and Colossus
+(1:02:13) – Jensen’s approach to engineering and leadership
+(1:07:38) – China
+(1:15:51) – TSMC and Taiwan
+(1:21:06) – NVIDIA’s moat
+(1:26:43) – AI data centers in space
+(1:30:31) – Will NVIDIA be worth $10 trillion?
+(1:40:40) – Leadership under pressure
+(1:54:26) – Video games
+(2:01:18) – AGI timeline
+(2:03:31) – Future of programming
+(2:17:02) – Consciousness
+(2:23:23) – Mortality

+

PODCAST LINKS:
+– Podcast Website: https://lexfridman.com/podcast
+– Apple Podcasts: https://apple.co/2lwqZIr
+– Spotify: https://spoti.fi/2nEwCF8
+– RSS: https://lexfridman.com/feed/podcast/
+– Podcast Playlist: https://www.youtube.com/playlist?list=PLrAXtmErZgOdP_8GztsuKi9nrraNbKKp4
+– Clips Channel: https://www.youtube.com/lexclips

]]>
+ + full + 153756299 +
+
+
diff --git a/tests/corpus/podcast/lex_fridman/expect.json b/tests/corpus/podcast/lex_fridman/expect.json new file mode 100644 index 0000000..648cd50 --- /dev/null +++ b/tests/corpus/podcast/lex_fridman/expect.json @@ -0,0 +1,8 @@ +{ + "type": "rss", + "version": "2.0", + "title": "Lex Fridman Podcast", + "items": 5, + "first_item_title": "#498 – Anthony Kaldellis: Roman Empire, Byzantine Empire, Rise & Fall of Empires", + "itunes_author": "Lex Fridman" +} diff --git a/tests/corpus/rdf/slashdot/data.xml b/tests/corpus/rdf/slashdot/data.xml new file mode 100644 index 0000000..0b4a798 --- /dev/null +++ b/tests/corpus/rdf/slashdot/data.xml @@ -0,0 +1,401 @@ + + + + + +Slashdot +https://slashdot.org/ +News for nerds, stuff that matters +en-us +Copyright Slashdot Media. All Rights Reserved. +2026-07-22T23:08:52+00:00 +Slashdot Media +feedback@slashdot.org +Technology +1970-01-01T00:00+00:00 +1 +hourly + + + + + + + + + + + + + + + + + + + + + + + +Slashdot +https://a.fsdn.com/sd/topics/topicslashdot.gif +https://slashdot.org/ + + +GM Is Quietly Becoming a Subscriptions Company +https://tech.slashdot.org/story/26/07/22/2010225/gm-is-quietly-becoming-a-subscriptions-company?utm_source=rss1.0mainlinkanon&utm_medium=feed +"General Motors has been pulling a Tim Cook and boosting its software and subscription business," reports Business Insider. During the automaker's Tuesday earnings call, executives said they're increasingly leaning on software subscriptions like OnStar and Super Cruise to generate high-margin recurring revenue long after customers buy their vehicles. GM says OnStar brought in about $800 million in the second quarter, while Super Cruise revenue grew about 70% year over year. From the report: GM says its software business keeps roughly 70 cents of every dollar it brings in. That's a rare level of profitability in the auto industry, as many car sales generate just four to 10 cents per sales dollar. [...] GM expects to add about 1 million OnStar subscribers this year, bringing the total close to 13 million. Super Cruise, GM's hands-free, eyes-on driving system, is growing even faster. GM added about 70,000 subscribers during the quarter and expects to end the year with more than 850,000. Revenue from the service increased about 70% from a year earlier. + +And a lot of drivers are sticking around after the free period ends. GM said between 30% and 40% of eligible owners continue paying after their included three-year Super Cruise subscription expires. [..] "We do think we have tremendous levers, multiple levers of growth," Barra said on the call. "We definitely think there's a lot of opportunity at GM to grow, improve margins, and become less cyclical." + +"Software and services are becoming increasingly important to how customers experience GM vehicles and how we deliver value beyond the initial purchase," a spokesperson previously told Business Insider. "As vehicles become more software-defined, we can introduce new digital experiences through updates and optional services rather than hardware changes."<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=GM+Is+Quietly+Becoming+a+Subscriptions+Company%3A+https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F22%2F2010225%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F22%2F2010225%2Fgm-is-quietly-becoming-a-subscriptions-company%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://tech.slashdot.org/story/26/07/22/2010225/gm-is-quietly-becoming-a-subscriptions-company?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050328&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T23:00:00+00:00 +transportation +subscribe-to-drive +technology +1 +1,1,1,1,0,0,0 + + +iOS 27 Code Suggests Apple Could Restrict Leased Devices After Missed Payments +https://apple.slashdot.org/story/26/07/22/2050240/ios-27-code-suggests-apple-could-restrict-leased-devices-after-missed-payments?utm_source=rss1.0mainlinkanon&utm_medium=feed +Code found in the iOS 27 beta suggests Apple is developing a system that could restrict leased iPhones when customers fall behind on payments. The discovery follows a recent Bloomberg report that Apple may soon launch a new "Apple Upgrade" leasing program, allowing customers to pay for hardware through monthly installments. 9to5Mac reports: The code describes a system called App Managed Features, which allows an authorized financing or provider app to enroll an iPhone and perform ongoing status checks. If the contract is no longer in good standing, Apple's system services can place the iPhone in "Restricted Mode," which blocks access to most apps until the payment or contract issue is resolved, while keeping a small set of apps available. The fixed allowlist currently found in the iOS 27 beta includes: Accessibility Reader, App Store, Health, Magnifier, Phone, Clock, Settings, Wallet, Passwords, and the Restricted Mode interface itself. + +Apps that can send critical alerts, such as Messages, Home, and certain medication or safety apps, may also remain accessible. However, the provider appears to have some control over those exceptions. The code does not appear to cancel, suspend, or otherwise modify App Store subscriptions associated with blocked apps. As a result, a subscription could continue billing even while access to its app is restricted. + +Additionally, there isn't a fixed number of missed payments that automatically triggers the restrictions. The financing provider's app decides when to lock the device based on its own policies. Finally, the new framework also introduces a new type of activation lock called "Partner Finance Lock," which is meant to prevent users from erasing, reselling, or stripping a restricted device for parts.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=iOS+27+Code+Suggests+Apple+Could+Restrict+Leased+Devices+After+Missed+Payments%3A+https%3A%2F%2Fapple.slashdot.org%2Fstory%2F26%2F07%2F22%2F2050240%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fapple.slashdot.org%2Fstory%2F26%2F07%2F22%2F2050240%2Fios-27-code-suggests-apple-could-restrict-leased-devices-after-missed-payments%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://apple.slashdot.org/story/26/07/22/2050240/ios-27-code-suggests-apple-could-restrict-leased-devices-after-missed-payments?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050348&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T22:00:00+00:00 +ios +lease-different +apple +11 +11,11,10,10,3,0,0 + + +Linux Kernel Team Publishes 432 CVEs In Two Days +https://linux.slashdot.org/story/26/07/22/2033256/linux-kernel-team-publishes-432-cves-in-two-days?utm_source=rss1.0mainlinkanon&utm_medium=feed +Ancient Slashdot reader alanw shares a post from the OSS Security mailing list, where sysadmin Jan Schaumann wonders what to do after the Linux kernel cranked out 432 CVEs in a little over 24 hours: "I understand the position that CVEs were always a flawed way to track or prioritize security changes... But this onslaught really shows it's not feasible to attempt to prioritize individual kernel changes. I'm not sure what to do here going forward." The Register reports: The nixCraft team speculated on social media that AI bug reports are a likely reason for all those kernel CVEs, which wouldn't be without precedent - Linus Torvalds himself said in May that the Linux kernel security mailing list had become "almost entirely unmanageable" due to AI-assisted bug hunting. Nonetheless, Torvalds has described AI as a useful tool for Linux development while still noting it can be a drag for maintainers, both from a workload standpoint and the fact "it keeps finding embarrassing bugs." [...] + +Unfortunately for Linux sysadmins, the position in which they find themselves in this current mess isn't one that's readily solved. CVEs might be a messy way to track and prioritize security updates, especially when hundreds of them are published over a short period, but without something better, it falls to IT and security teams to determine which vulnerabilities affect their systems and which kernel updates they need to deploy. Senior kernel maintainer Greg Kroah-Hartman replied to Jan's post, pushing back on the idea that the kernel's CVE volume is uniquely unmanageable. The kernel isn't special, he argues -- companies everywhere are finally realizing they need to re-evaluate how they update all of their systems and devices, something that's traditionally been "woefully ignored." + +On the "just always update" approach, Greg says that's precisely what the kernel community endorses: "This is what the kernel developer community recommends and supports. If you want support from us, do this." Can't manage it yourself? Pay a company for support, or "just use Debian or Yocto as their security practices are amazing." He points to Android as proof the approach scales, calling it "the largest deployment of software in the world" -- billions of devices kept updated "with one very-overworked developer guiding it all." + +As for reviewing every CVE individually, he notes this can be largely automated by intersecting the files a CVE touches with the files you actually build, which typically trims the relevant set "down to about 10% of the overall total" -- the approach enterprise distros already take for their customers. Panic-mode selective patching gets a blunt "Good luck with that!" -- regulations like the EU's Cyber Resilience Act are set to legislate that habit away ("rightfully so," in his view), and "your insurance company might wish to have a talk with you as well." + +Greg also warns the flood isn't over: "The number of llm-found issues is only on the rise right now, it's going to be a very long 18 months at the least to dig ourselves out of this mess, and people had BETTER be updating their systems all along the way if they expect to be secure in any way." As for the 432-CVE burst itself, he explains it was simply him catching up on a weeks-old, publicly visible review queue over the weekend -- delayed by "a perfect storm of 6 weeks straight of conferences and vacations" -- so it shouldn't have come as a surprise to anyone watching the public git repo.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Linux+Kernel+Team+Publishes+432+CVEs+In+Two+Days%3A+https%3A%2F%2Flinux.slashdot.org%2Fstory%2F26%2F07%2F22%2F2033256%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Flinux.slashdot.org%2Fstory%2F26%2F07%2F22%2F2033256%2Flinux-kernel-team-publishes-432-cves-in-two-days%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://linux.slashdot.org/story/26/07/22/2033256/linux-kernel-team-publishes-432-cves-in-two-days?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050346&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T21:00:00+00:00 +ai +CVE-avalanche +linux +20 +20,20,19,19,0,0,0 + + +Apple Partners With Klarna To Offer iPhones, Macs On a Subscription Basis +https://hardware.slashdot.org/story/26/07/22/1958256/apple-partners-with-klarna-to-offer-iphones-macs-on-a-subscription-basis?utm_source=rss1.0mainlinkanon&utm_medium=feed +Apple is reportedly launching a Klarna financing deal that will let U.S. customers spread the cost of devices over up to three years, pushing the company closer to a hardware-as-a-service model. "The only thing you don't get under the new arrangement is AppleCare, for which you'll allegedly need to pay extra," notes Computerworld. From the report: The introduction of the scheme gives consumers a way to purchase the company's popular high-end devices when they are introduced -- no doubt,at higher cost -- this fall. [...] A combination of changed customer habits and external threat means the stars are now aligned for hardware-as-a-service models. "Reframing a device as a low monthly payment protects that [upgrade] cadence and allows Apple to start marketing their products as device-as-a-service to consumers, which no other vendor was ever able to do," [IDC analyst Francisco Jeronimo] wrote to me. + +There is a one-more-thing aspect to this: the products are effectively being leased, a new approach that will give Apple a stronger grip on EOL devices, helping it grab more of them for refurbishment, resale, and recycling. Over time, this will give the company a much stronger grip on the lucrative second-user market that exists around Apple equipment, even while for almost every consumer product we find the life we want is something we can rent, but probably can't afford to own. + +The other solid reason to take a partnership approach is risk management. Apple had intended to develop its own buy-now, pay-later scheme via Apple Pay Later, but abandoned that plan as it became riskier with rising bank rates. "Also, by backing the program with Klarna rather than reviving the in-house subscription plan it shelved in 2024, Apple captures the demand upside without taking the credit risk onto its own balance sheet," Jeronimo said. +"Apple Upgrade lands at precisely the moment Apple needs it," Jeronimo wrote in a note seen by Computerworld. "Having just pushed Mac and iPad prices up on the back of the memory shortage, with iPhone increases widely expected in September -- as well as the new iPhone foldable expected at $2,500 -- Apple's real risk is that rising prices even further can impact the upgrade cycle."<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Apple+Partners+With+Klarna+To+Offer+iPhones%2C+Macs+On+a+Subscription+Basis%3A+https%3A%2F%2Fhardware.slashdot.org%2Fstory%2F26%2F07%2F22%2F1958256%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fhardware.slashdot.org%2Fstory%2F26%2F07%2F22%2F1958256%2Fapple-partners-with-klarna-to-offer-iphones-macs-on-a-subscription-basis%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://hardware.slashdot.org/story/26/07/22/1958256/apple-partners-with-klarna-to-offer-iphones-macs-on-a-subscription-basis?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050326&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T20:00:00+00:00 +money +own-nothing-upgrade-everything +hardware +36 +36,36,34,31,8,2,2 + + +The Army Is Burning Through Its AI Tokens +https://tech.slashdot.org/story/26/07/22/179241/the-army-is-burning-through-its-ai-tokens?utm_source=rss1.0mainlinkanon&utm_medium=feed +An anonymous reader quotes a report from Wired: A little over a month after the Department of Defense (DOD) bragged that nearly half of its 3.5 million employees were using AI at work, members of the Army's Combat Capabilities Development Command (DEVCOM) received an email informing them that they were burning through tokens, and needed to limit use. "Although the Army CIO announced in May 2026 that they were offering unlimited tokens, by mid-June the Army CIO pool was exhausted of tokens and had to re-establish limits," the email reads. The email goes on to say that although the Army has chosen to renew token usage at "its current levels," it's unclear "if the Army CIO pool will be renewed after 1 Oct." + +The Army uses Ask Sage, a multimodal generative AI platform where users can run different large language models (LLMs), including Alphabet's Gemini, Meta's Llama, and OpenAI's ChatGPT. "Apparently the whole Army burned through the whole year of tokens for just one service," says an Army employee who spoke to WIRED [...]. The Army employee says that the Army has been pushing its workers to lean into using generative AI. Employees were given an allotment of at least 200,000 tokens per month, according to emails viewed by WIRED, and were automatically allocated more if they burned through their initial allotment. Employees who had signed up for Ask Sage but were not regularly using it would receive emails encouraging them to use more of their allocated tokens. + +In order to use Ask Sage, the Army had access to 100,000,000 tokens as part of an annual subscription to an "enterprise pack." Tokens represent a unit of output, either in text or image, from an LLM. For the Ask Sage tool, a single token equates to about 3.7 characters, according to documents viewed by WIRED. The Defense Department burned through some 20 billion tokens per day during the 38-day Operation Epic Fury in Iran, according to Breaking Defense. It's unclear if the tokens used by regular DOD employees are drawn from the same pool as those who might be using AI tools on classified or secret information.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=The+Army+Is+Burning+Through+Its+AI+Tokens%3A+https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F22%2F179241%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F22%2F179241%2Fthe-army-is-burning-through-its-ai-tokens%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://tech.slashdot.org/story/26/07/22/179241/the-army-is-burning-through-its-ai-tokens?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050180&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T19:00:00+00:00 +military +supply-and-demand +technology +45 +45,45,41,37,12,10,4 + + +Microsoft Announces Xbox Backward Compatibility For PC +https://games.slashdot.org/story/26/07/22/1659215/microsoft-announces-xbox-backward-compatibility-for-pc?utm_source=rss1.0mainlinkanon&utm_medium=feed +Microsoft has announced Xbox Backward Compatibility for PC, a new preservation program that will let players run select classic Xbox games on Windows PCs and handhelds like the ROG Xbox Ally. Tom's Hardware reports: "This marks the beginning of a broader effort to preserve XBOX games from the past and bring them to PC over time," the company said in a blog post authored by Xbox "VP next generation" Jason Ronald. Alongside backward compatibility, the company says games will also include new features. The four titles in the announcement are: BLiNX: The Time Sweeper; Conker: Live and Reloaded; Crimson Skies: High Road to Revenge; and Fuzion Frenzy. + +You can now buy all of these games on PC, and they're also included in all Xbox Game Pass plans. Anyone who already owns these titles digitally on console can also now play them on PC or handheld, with support for Xbox Play Anywhere and Xbox Cloud Gaming. Crucially, this appears to be a digital-only preservation effort, so it won't help anyone who only owns physical copies of these games. Xbox has reportedly been testing a way to digitize physical games as far back as Xbox One, but that hasn't yet materialized yet. + +Alongside the preserved original gameplay, Xbox claims these games will let users customize graphics settings, with up to 4x resolution scaling, VSync support, Fullscreen and Windowed modes, anisotropic filtering, and enhanced anti-aliasing, with more features to come in the future. Notably, Xbox will add achievements to select original Xbox games on console and PC.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Microsoft+Announces+Xbox+Backward+Compatibility+For+PC%3A+https%3A%2F%2Fgames.slashdot.org%2Fstory%2F26%2F07%2F22%2F1659215%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fgames.slashdot.org%2Fstory%2F26%2F07%2F22%2F1659215%2Fmicrosoft-announces-xbox-backward-compatibility-for-pc%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://games.slashdot.org/story/26/07/22/1659215/microsoft-announces-xbox-backward-compatibility-for-pc?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050176&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T18:00:00+00:00 +xbox +forward-looking +games +27 +27,27,25,24,3,1,1 + + +Samsung Galaxy Z Fold 8 Announced to Compete With Future iPhone Fold +https://mobile.slashdot.org/story/26/07/22/1652232/samsung-galaxy-z-fold-8-announced-to-compete-with-future-iphone-fold?utm_source=rss1.0mainlinkanon&utm_medium=feed +At a Galaxy Unpacked event in London today, Samsung unveiled a new foldable smartphone designed to compete with the still-unannounced iPhone Fold. Called the Galaxy Z Fold 8, it features a wider 4:3 form factor with a 7.6-inch inner AMOLED display, Snapdragon 8 Elite Gen 5 for Galaxy chip, up to 16GB of RAM and 1TB of storage. "The inner display has enough extra real estate for multi-tasking and entertainment," reports Mashable, which got a chance to try the new foldable ahead of the event. "And if you're worried about a crease in the middle of the display, don't be. We got up close with the device, and the crease fully disappears when the display is activated." From the report: As stated above, the main distinguishing factor of the new Z Fold 8 is its wider 4:3 proportions compared to the Z Fold 8 Ultra. If the latter is a book-style foldable, the former can almost be called a tablet-style foldable, instead. We think the wide screen will serve even better for reading books or multi-tasking with several apps open at once. + +Also, at 201g, Samsung is really emphasizing how light the device is. The company is calling it the "world's lightest fold ever." And while the device may be light, its no lightweight. Samsung's newest foldable features Flex Titanium technology, which makes the Galaxy Z Fold 8 more durable. Speaking of durability, it also boasts Samsung's advantage hinge technology, Armor Flexhinge. + +The Z Fold 8 feels like a solid middle point between the premium Z Fold 8 Ultra and the comparatively affordable Z Flip 8. All three phones use the same chip, but the Z Fold 8's 4,800mAh battery is smaller than that of the Z Fold 8 Ultra. However, the Z Fold 8 does have an option for 16GB RAM and 1TB of storage, similar to the Ultra.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Samsung+Galaxy+Z+Fold+8+Announced+to+Compete+With+Future+iPhone+Fold%3A+https%3A%2F%2Fmobile.slashdot.org%2Fstory%2F26%2F07%2F22%2F1652232%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fmobile.slashdot.org%2Fstory%2F26%2F07%2F22%2F1652232%2Fsamsung-galaxy-z-fold-8-announced-to-compete-with-future-iphone-fold%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://mobile.slashdot.org/story/26/07/22/1652232/samsung-galaxy-z-fold-8-announced-to-compete-with-future-iphone-fold?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24050166&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T17:00:00+00:00 +cellphones +new-and-shiny +mobile +13 +13,13,13,11,2,0,0 + + +LG To Ban Residential Proxies From Smart TV Apps +https://entertainment.slashdot.org/story/26/07/22/0426218/lg-to-ban-residential-proxies-from-smart-tv-apps?utm_source=rss1.0mainlinkanon&utm_medium=feed +An anonymous reader quotes a report from KrebsOnSecurity: The home appliance giant LG Electronics USA said this week it plans to suspend any apps built for its smart TVs that turn one's television into an always-on residential proxy node. The move comes less than a month after researchers found that more than 42 percent of games and other apps available for download on LG's webOS store allow unknown third-parties to route their Internet traffic through a user's TV. On July 2, [KrebsOnSecurity] featured research by the security firm Spur that examined the prevalence of residential proxy software development kits (SDKs) in smart TV apps. Spur found more than 42 percent of apps available for download on LG smart TVs include SDKs that turn one's television in a proxy node indefinitely, and that more than a quarter of the apps made for Samsung's Tizen operating system had similar residential proxy components. + +Responding to questions about Spur's research, LG Senior Vice President John Taylor told KrebsOnSecurity the company was working with app developers to remove the residential proxy option from their apps on the webOS platform. Developers that fail to comply, he said, will find their apps suspended. "A residential proxy network is not an intended use for LG smart TVs, and LG Electronics is working with developers to remove the residential proxy option from their apps on the webOS platform," Taylor said. "If this option is not removed, these apps will be suspended." Taylor said LG is committed to keeping residential proxy networks out of its smart TV apps going forward, and that the company's review of those apps is "well underway now." + +"As part of our ongoing efforts to enhance platform quality and the user experience, LG will continue to strengthen our evaluation process for developer-submitted apps, including those that incorporate residential proxy SDKs," Taylor wrote in an emailed statement. [...] "A one-time consent prompt buried in a TV app is not a substitute for meaningful transparency, ongoing control, and platform oversight," Spur's Trevor Sutter wrote. "The risk is amplified when consent comes from individuals within the household who use the device but shouldn't give consent, such as minors." LG is also facing criticism for monitors that automatically install software promoting paid McAfee subscriptions through Windows Update without user approval.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=LG+To+Ban+Residential+Proxies+From+Smart+TV+Apps%3A+https%3A%2F%2Fentertainment.slashdot.org%2Fstory%2F26%2F07%2F22%2F0426218%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fentertainment.slashdot.org%2Fstory%2F26%2F07%2F22%2F0426218%2Flg-to-ban-residential-proxies-from-smart-tv-apps%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://entertainment.slashdot.org/story/26/07/22/0426218/lg-to-ban-residential-proxies-from-smart-tv-apps?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049684&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T16:00:00+00:00 +tv +cease-and-desist +entertainment +33 +33,33,29,28,10,3,2 + + +Jack Dorsey Takes On Slack and GitHub With New AI Workplace Platform 'Buzz' +https://news.slashdot.org/story/26/07/22/040209/jack-dorsey-takes-on-slack-and-github-with-new-ai-workplace-platform-buzz?utm_source=rss1.0mainlinkanon&utm_medium=feed +Jack Dorsey's Block has launched Buzz, an open-source workplace collaboration platform that combines messaging, project management, and software development workflows for teams of both humans and AI agents. Dorsey described Buzz as "a new groupchat platform for teams of people and agents of all sizes" that is "model-agnostic, decentralized, self-sovereign and open source." SmartCompany reports: According to the Buzz website, users can invite specialized AI agents into team chats, allowing them to collaborate with employees and even other AI agents. From there, they can reportedly move directly from discussions into planning, coding, pull requests and project management without switching between multiple applications. + +Buzz also aims to replace parts of GitHub by bringing software development workflows directly into the platform. Teams can plan work, write code, review pull requests and manage Git projects without jumping between separate collaboration and development tools. [...] In practice, that means businesses aren't locked into a single AI provider. Organisations can self-host Buzz, customize it to suit their own workflows and choose whichever AI models best fit their needs.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Jack+Dorsey+Takes+On+Slack+and+GitHub+With+New+AI+Workplace+Platform+'Buzz'%3A+https%3A%2F%2Fnews.slashdot.org%2Fstory%2F26%2F07%2F22%2F040209%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fnews.slashdot.org%2Fstory%2F26%2F07%2F22%2F040209%2Fjack-dorsey-takes-on-slack-and-github-with-new-ai-workplace-platform-buzz%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://news.slashdot.org/story/26/07/22/040209/jack-dorsey-takes-on-slack-and-github-with-new-ai-workplace-platform-buzz?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049668&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T15:00:00+00:00 +opensource +jack-of-all-workflows +news +15 +15,15,14,11,4,1,0 + + +Long Presumed Dead, a Thriving Coral Reef Is Discovered in West Africa +https://news.slashdot.org/story/26/07/22/049248/long-presumed-dead-a-thriving-coral-reef-is-discovered-in-west-africa?utm_source=rss1.0mainlinkanon&utm_medium=feed +Scientists have rediscovered a healthy coral reef off the coast of Benin more than 60 years after surveyors first hinted at its existence. "At least eight coral types and eight fish species have formed a thriving ecosystem on this long-forgotten site," reports Inside Climate News. "What they had captured was a wealth of marine life: six types of soft coral; two black corals; and eight species of sheltering fish, from golden African snappers to the Monrovia doctorfish." From the report: While no coral samples have yet been extracted, researchers have classified the site as a mesophotic coral ecosystem (MCE). Such systems are light-dependent communities existing at the lower limits of reef-building corals. At more than 175 feet below the surface, the coral garden they discovered appears patchily scattered on a rocky substrate. Bridging the gap between shallow reefs and deeper benthic habitats, MCEs are home to distinct species and unique environmental conditions. While scientists are increasingly recognizing their ecological and climatic importance, they remain among the least explored elements of tropical and subtropical marine biodiversity. And this one has real potential to unlock new information about coral history. + +"Since it's an undisturbed marine ecosystem, it can help through carbon dating or paleoclimatic study to tell us which kind of climate system has occurred here in the past," said [Gerard Zinzindohoue, the project lead for Coral Reefs Rediscovering &amp; Exploration in Benin]. "It's better to know the past to help explain the present, and help know which kind of direction we can take in the future." In addition to the blackbar soldierfish, West African goatfish, and Guinean angelfish filmed darting through the reef, the discovery presents potential conservation claims for other marine life. "We will be advocating for its full protection, perhaps by setting up a marine protected area around it," said [Houangninan Midinoudewa, an oceanographic researcher at the Benin Marine Conservation Club], who specializes in elasmobranchs -- the study of sharks, rays, and skates. + +Midinoudewa is currently submitting an application to designate the area as an Important Shark and Ray Area with the International Union for Conservation of Nature. Based on the knowledge of local fishermen, Midinoudewa is confident the reef is home to sawback angel sharks, silky sharks, brown skates, and marbled stingrays. The team behind the discovery hopes this will spark a wave of similar discoveries in the waters off West Africa, an area of the world underserved by scientific research projects. "I hope the Gulf of Guinea will be a hub for research because we know our resources are being exploited and people [need to] know exactly what we have and why we should care," said Midinoudewa. Zinzindohoue agrees: "We don't need to wait for others to come to our country to show us what is under our sea. We are the ones who must take responsibility."<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Long+Presumed+Dead%2C+a+Thriving+Coral+Reef+Is+Discovered+in+West+Africa%3A+https%3A%2F%2Fnews.slashdot.org%2Fstory%2F26%2F07%2F22%2F049248%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fnews.slashdot.org%2Fstory%2F26%2F07%2F22%2F049248%2Flong-presumed-dead-a-thriving-coral-reef-is-discovered-in-west-africa%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://news.slashdot.org/story/26/07/22/049248/long-presumed-dead-a-thriving-coral-reef-is-discovered-in-west-africa?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049670&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T11:00:00+00:00 +earth +hide-and-seek +news +16 +16,16,15,15,2,1,1 + + +OpenAI Says Its AI Models Acted On Its Own In An 'Unprecedented' Hack +https://it.slashdot.org/story/26/07/22/0348206/openai-says-its-ai-models-acted-on-its-own-in-an-unprecedented-hack?utm_source=rss1.0mainlinkanon&utm_medium=feed +"GPT-5.6 Sol and an 'even more capable' model used stolen credentials and exploited vulnerabilities in the Hugging Face API to obtain secret information used to cheat on evaluations," writes longtime Slashdot reader Dr. Bombay. The Associated Press reports: "We had a significant security incident during evaluation of our models," OpenAI CEO Sam Altman said in a statement posted on social media. AI startup Hugging Face said last week that it had detected an intrusion into its data processing systems that it suspected was caused by an AI agent autonomously acting on its own. "We suspected last week's cyberattack might have come from a frontier lab, given the sophistication of the agent," Hugging Face co-founder and CEO Clement Delangue said in a statement. "Turns out it did!" + +[...] "AI is accelerating the discovery and exploitation of vulnerabilities," OpenAI said in its statement Tuesday. "The primary lesson from this incident is that model security and safety must keep pace with rapidly advancing capabilities." Delangue said he spent the past 24 hours working with OpenAI, "and we strongly believe there was no malicious intent on their part. It's quite mind-blowing that all of this happened autonomously!" Delangue added that it "might be the first incident of its kind."<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=OpenAI+Says+Its+AI+Models+Acted+On+Its+Own+In+An+'Unprecedented'+Hack%3A+https%3A%2F%2Fit.slashdot.org%2Fstory%2F26%2F07%2F22%2F0348206%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fit.slashdot.org%2Fstory%2F26%2F07%2F22%2F0348206%2Fopenai-says-its-ai-models-acted-on-its-own-in-an-unprecedented-hack%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://it.slashdot.org/story/26/07/22/0348206/openai-says-its-ai-models-acted-on-its-own-in-an-unprecedented-hack?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049658&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T07:00:00+00:00 +security +that's-a-first +it +142 +142,141,125,115,36,20,12 + + +France Becomes First European Country To Ban Social Media Access For Under-15s +https://tech.slashdot.org/story/26/07/21/216206/france-becomes-first-european-country-to-ban-social-media-access-for-under-15s?utm_source=rss1.0mainlinkanon&utm_medium=feed +An anonymous reader quotes a report from The Guardian: France's parliament has approved a bill banning social media access for children under 15, making it the first European country to bar children from apps such as TikTok. The president, Emmanuel Macron, has championed the ban as a key reform of his final term in office and pledged to enforce it by September. "France is leading the way in Europe when it comes to protecting our children and teenagers," Macron said in a video posted on social media, hailing "a major step forward." + +He thanked members of parliament for backing the legislation on Tuesday. "The Constitutional Council must now rule on it, and then it will be time to take action to make this measure a reality and protect our children online," he added on X. After approval by the Senate earlier on Tuesday, members of the National Assembly passed the bill by 279 votes to 81. A growing number of countries are taking steps to restrict social media access amid multiplying warnings over its harmful effects on children. + +The ban was to be introduced in two stages, with under-15s blocked from creating new accounts from September 1. The ban would apply to existing accounts from January 2027, according to the legislation. The digital minister, Anne Le Henanff, said before the vote that the timeline was realistic, "because age-verification tools already exist" and others are still in the works, and the onus was on the platforms to impose the rule. "For four months, all of us in France will have to prove our age," she told journalists. "If someone is under 15, the account will be closed." The minister also gave assurances that users' personal data would be protected.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=France+Becomes+First+European+Country+To+Ban+Social+Media+Access+For+Under-15s%3A+https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F21%2F216206%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F21%2F216206%2Ffrance-becomes-first-european-country-to-ban-social-media-access-for-under-15s%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://tech.slashdot.org/story/26/07/21/216206/france-becomes-first-european-country-to-ban-social-media-access-for-under-15s?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049408&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-22T03:30:00+00:00 +eu +leading-the-way +technology +120 +120,115,102,87,15,7,3 + + +Airbus Migrating 70 Critical Apps From AWS to France's Scaleway +https://slashdot.org/story/26/07/21/2050242/airbus-migrating-70-critical-apps-from-aws-to-frances-scaleway?utm_source=rss1.0mainlinkanon&utm_medium=feed +Airbus is moving 70 critical applications from AWS to French cloud provider Scaleway as part of a broader digital sovereignty push to keep sensitive data "under European control." Eventually, the migration will cover 900 applications, including ERP, CRM, manufacturing execution, and product lifecycle management systems. Airbus says it will, however, continue using U.S. providers for less sensitive workloads. "We do not intend to move away from all non European solutions; we balance our choices based on the criticality of the data," the company said. The Register reports: Catherine Jestin, head of digital at Airbus, told us on Thursday: "The selection of Scaleway is a combination of a very strong technical answer and a very strong commercial offer making it competitive compared to hyperscalers' public cloud offerings. In addition, Scaleway is committed to involving Airbus in the definition of its future product roadmap." "The objective is to host Airbus's most critical applications (those required for the Minimum Viable Company). This represents 900 applications and we will start with 70 of them today hosted on AWS." + +Applications being sent to Scaleway include ERP, manufacturing execution systems, CRM, and product lifecycle management. Finding a cloud provider to host its most sensitive applications for defense and industrial workloads was not a certainty when the process began, Airbus told us last year, because European cloud providers do not have the scale of their US rivals. + +Jestin said Airbus will continue to work with AWS. Skywise, a platform that aggregates and analyzes aviation data, and Case Management Assistant for customers' technical queries will continue to be hosted by AWS. In a statement, she said: "By integrating a trusted, high performance, cloud environment that keeps our critical data assets shielded from foreign extraterritorial laws, we are ensuring that our digital infrastructure keeps pace with our aerospace innovation, while maintaining control and resilience of our industrial operations."<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Airbus+Migrating+70+Critical+Apps+From+AWS+to+France's+Scaleway%3A+https%3A%2F%2Fslashdot.org%2Fstory%2F26%2F07%2F21%2F2050242%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fslashdot.org%2Fstory%2F26%2F07%2F21%2F2050242%2Fairbus-migrating-70-critical-apps-from-aws-to-frances-scaleway%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://slashdot.org/story/26/07/21/2050242/airbus-migrating-70-critical-apps-from-aws-to-frances-scaleway?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049394&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-21T23:00:00+00:00 +cloud +digital-sovereignty-push +slashdot +87 +87,84,73,68,22,15,11 + + +Tesla Robotaxis Go To Florida +https://tech.slashdot.org/story/26/07/21/2037254/tesla-robotaxis-go-to-florida?utm_source=rss1.0mainlinkanon&utm_medium=feed +Tesla says it is launching its Robotaxi service in Orlando and Tampa, though it has not shared fleet sizes or whether customers can immediately hail rides. The Verge notes the announcement lands on Tesla earnings day and comes as the company's robotaxi rollout remains far smaller than Elon Musk previously projected, with tracker data showing only a small number of unsupervised vehicles operating in existing cities. From the report: [B]ack in May, Tesla had five unsupervised robotaxis in Dallas, six in Houston, and 29 in Austin. As of today, there are only 17 unsupervised vehicles in Austin, four in Dallas, and zero in Houston, according to the Robotaxi Tracker. By comparison, Waymo is estimated to have over 3,500 fully driverless vehicles in operation across over 10 cities. + +Like Waymo, Tesla typically introduces robotaxis to a new city with a safety driver behind the wheel. Unlike Waymo, sometimes Tesla moves that person over to the passenger seat with access to a kill switch should anything go wrong. The company has been inconsistent in how it rolls out its robotaxis to new markets. The numbers go up and down, with zero explanation from the company as to why. + +[...] By adding new cities, Tesla is clearly trying to sell investors on the idea that its robotaxi project is growing. But the fluctuating fleet size, inconsistency in supervised vs unsupervised vehicles, and long wait times tell a very different story.<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Tesla+Robotaxis+Go+To+Florida%3A+https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F21%2F2037254%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Ftech.slashdot.org%2Fstory%2F26%2F07%2F21%2F2037254%2Ftesla-robotaxis-go-to-florida%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://tech.slashdot.org/story/26/07/21/2037254/tesla-robotaxis-go-to-florida?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049392&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-21T22:00:00+00:00 +transportation +rolling-out +technology +53 +53,52,46,37,7,3,2 + + +Firefox 153 Released +https://news.slashdot.org/story/26/07/21/2022247/firefox-153-released?utm_source=rss1.0mainlinkanon&utm_medium=feed +Longtime Slashdot reader williamyf writes: FireFox 153 was released today. The most important user-facing changes are improvements to PDF handling (you can now merge PDFs and add images to them), and HDR video playback (on Windows, provided HDR is active systemwide). Other under-the-hood changes include browser-wide containers and QWAC support. The full list is in the change notes. + + But the most important feature is that this version is an ESR and, therefore, defines the ESR feature set for the next year. Why is being an ESR so important, you ask? + + 1.) ESR, rather than "normal" (a.k.a. Rapid Release), Firefox is the out-of-the-box browser for many important distros, including Debian, RHEL, Kali, Tails, SUSE Linux Enterprise, Slackware, and others. + + 2.) Many organizations, large and small, standardize on Firefox ESR as their default browser, regardless of the default browser included with their OS. + + 3.) Firefox ESR is the basis for many downstream projects, such as Waterfox and KaiOS. All these projects will inherit, for a year, whatever ESR brings to the table today. + + 4.) Many ISVs and SaaS providers, if they certify their wares for Firefox at all, certify for the ESR version only. + + Please note that ESR 153 will not be offered as an automatic update until two months from now (ESR 140 will still be supported). If you want it now, you will need to download and install it manually. + + Also of note, ESR 115 will be supported until March 2027. If you use an unsupported version of macOS or Windows (like Windows 7 or 8.x), this is the version to get. However, even Mozilla cautions against running a supported browser on an unsupported OS: "Note that Microsoft ended official support for Windows 7, 8, and 8.1 in January 2023. Unsupported operating systems receive no security updates and have known vulnerabilities. Without official support from Microsoft, maintaining Firefox for outdated operating systems becomes costly for Mozilla and risky for users."<p><div class="share_submission" style="position:relative;"> +<a class="slashpop" href="http://twitter.com/home?status=Firefox+153+Released%3A+https%3A%2F%2Fnews.slashdot.org%2Fstory%2F26%2F07%2F21%2F2022247%2F%3Futm_source%3Dtwitter%26utm_medium%3Dtwitter"><img src="https://a.fsdn.com/sd/twitter_icon_large.png"></a> +<a class="slashpop" href="http://www.facebook.com/sharer.php?u=https%3A%2F%2Fnews.slashdot.org%2Fstory%2F26%2F07%2F21%2F2022247%2Ffirefox-153-released%3Futm_source%3Dslashdot%26utm_medium%3Dfacebook"><img src="https://a.fsdn.com/sd/facebook_icon_large.png"></a> + + + +</div></p><p><a href="https://news.slashdot.org/story/26/07/21/2022247/firefox-153-released?utm_source=rss1.0moreanon&amp;utm_medium=feed">Read more of this story</a> at Slashdot.</p><iframe src="https://slashdot.org/slashdot-it.pl?op=discuss&amp;id=24049378&amp;smallembed=1" style="height: 300px; width: 100%; border: none;"></iframe> +BeauHD +2026-07-21T21:00:00+00:00 +firefox +new-and-improved +news +47 +47,44,38,34,9,4,3 + + +Search Slashdot +Search Slashdot stories +query +https://slashdot.org/search.pl + + \ No newline at end of file diff --git a/tests/corpus/rdf/slashdot/expect.json b/tests/corpus/rdf/slashdot/expect.json new file mode 100644 index 0000000..cffa9e7 --- /dev/null +++ b/tests/corpus/rdf/slashdot/expect.json @@ -0,0 +1,7 @@ +{ + "type": "rdf", + "encoding": "iso-8859-1", + "title": "Slashdot", + "items": 15, + "first_item_title": "GM Is Quietly Becoming a Subscriptions Company" +} diff --git a/tests/corpus/rss/bbc_news/data.xml b/tests/corpus/rss/bbc_news/data.xml new file mode 100644 index 0000000..d825961 --- /dev/null +++ b/tests/corpus/rss/bbc_news/data.xml @@ -0,0 +1,306 @@ + + + <![CDATA[BBC News]]> + + https://www.bbc.co.uk/news + + https://news.bbcimg.co.uk/nol/shared/img/bbc_news_120x60.gif + BBC News + https://www.bbc.co.uk/news + + RSS for Node + Wed, 22 Jul 2026 23:16:01 GMT + + + + 15 + + <![CDATA[PC Harper's widow criticises early prisoner release plan as Burnham to review scheme]]> + + https://www.bbc.co.uk/news/articles/c0ejwedl1gno?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c0ejwedl1gno#0 + Wed, 22 Jul 2026 15:43:55 GMT + + + + <![CDATA[Most bus fares in England to be capped at £2 from January]]> + + https://www.bbc.co.uk/news/articles/cz64l78n5vpo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cz64l78n5vpo#0 + Wed, 22 Jul 2026 12:26:03 GMT + + + + <![CDATA[Ukrainian drones hit Russian online giant retailer Wildberries for second time]]> + + https://www.bbc.co.uk/news/articles/c36de9n4pxpo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c36de9n4pxpo#0 + Wed, 22 Jul 2026 14:52:25 GMT + + + + <![CDATA[Ex-Southern Water boss among four charged over alleged plan to manipulate water quality tests]]> + + https://www.bbc.co.uk/news/articles/c36d0njy7jjo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c36d0njy7jjo#0 + Wed, 22 Jul 2026 16:07:15 GMT + + + + <![CDATA[OpenAI says its AI went rogue and launched 'unprecedented' cyber-attack]]> + + https://www.bbc.co.uk/news/articles/c3ek3gvdnj3o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c3ek3gvdnj3o#0 + Wed, 22 Jul 2026 11:40:11 GMT + + + + <![CDATA[Glasgow set to welcome the world for scaled back Commonwealth Games]]> + + https://www.bbc.co.uk/news/articles/czj8zjnzw4po?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/czj8zjnzw4po#0 + Wed, 22 Jul 2026 21:51:18 GMT + + + + <![CDATA[Prince George enjoys coastal fun in new video as he becomes a teenager]]> + + https://www.bbc.co.uk/news/articles/cm2ge7z0708o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cm2ge7z0708o#0 + Wed, 22 Jul 2026 14:42:32 GMT + + + + <![CDATA[Police formally investigate woman, 70, after Brit stabbed to death in French village]]> + + https://www.bbc.co.uk/news/articles/cwye7lv2endo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cwye7lv2endo#0 + Wed, 22 Jul 2026 19:03:08 GMT + + + + <![CDATA[Trump threatens to target Iran's bridges and power plants if Hormuz attacks persist]]> + + https://www.bbc.co.uk/news/articles/cdrv0p37k8jo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cdrv0p37k8jo#0 + Wed, 22 Jul 2026 22:17:54 GMT + + + + <![CDATA[Mamdani backs off pledge to arrest Netanyahu citing lack of authority]]> + + https://www.bbc.co.uk/news/articles/c204p64pqzno?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c204p64pqzno#0 + Wed, 22 Jul 2026 17:54:57 GMT + + + + <![CDATA[Wreckage of Pan Am plane that shaped aviation safety found 74 years on]]> + + https://www.bbc.co.uk/news/articles/cdrvyllxj71o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cdrvyllxj71o#0 + Wed, 22 Jul 2026 15:50:17 GMT + + + + <![CDATA[Blocked by censors, China's animal lovers take fight against abuse offline and overseas]]> + + https://www.bbc.co.uk/news/articles/cqx7wd3x420o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cqx7wd3x420o#1 + Wed, 22 Jul 2026 22:03:43 GMT + + + + <![CDATA[Scottish Labour at a crossroads again as Sarwar jumps ship]]> + + https://www.bbc.co.uk/news/articles/cy078g8g2pvo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cy078g8g2pvo#1 + Wed, 22 Jul 2026 22:41:27 GMT + + + + <![CDATA[The Odyssey film fans inspired to go back to the source]]> + + https://www.bbc.co.uk/news/articles/cp9en982n3do?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cp9en982n3do#1 + Wed, 22 Jul 2026 12:56:45 GMT + + + + <![CDATA[Indian police cracked down on 'cockroach' protesters. They went home and made memes about it]]> + + https://www.bbc.co.uk/news/articles/c3ek3l9gp7go?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c3ek3l9gp7go#1 + Wed, 22 Jul 2026 22:13:24 GMT + + + + <![CDATA[I travel four hours on a bus per day - the bus fare cap will save me £500 a year]]> + + https://www.bbc.co.uk/news/articles/clyv4y3xdvgo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/clyv4y3xdvgo#1 + Wed, 22 Jul 2026 17:07:05 GMT + + + + <![CDATA[A year after deadly jet crash at Bangladesh school, families demand answers]]> + + https://www.bbc.co.uk/news/articles/cx2j7jgg1z1o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cx2j7jgg1z1o#1 + Wed, 22 Jul 2026 22:04:07 GMT + + + + <![CDATA[Five big names to look out for at Glasgow 2026]]> + + https://www.bbc.co.uk/sport/articles/c9w0r0k1n5qo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/articles/c9w0r0k1n5qo#1 + Wed, 22 Jul 2026 07:46:47 GMT + + + + <![CDATA[No helicopters to fight wildfires as 'crisis management' plans activated ]]> + + https://www.bbc.co.uk/news/articles/cx25pgg440wo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cx25pgg440wo#3 + Wed, 22 Jul 2026 16:44:48 GMT + + + + <![CDATA[Natalie Fleet leaves 'triggering' safeguarding minister role]]> + + https://www.bbc.co.uk/news/articles/cq56g2n083do?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cq56g2n083do#3 + Wed, 22 Jul 2026 17:33:56 GMT + + + + <![CDATA[Watch: Louvre reopens gallery without crown jewels after heist]]> + + https://www.bbc.co.uk/news/videos/c1m15l8kgejo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/videos/c1m15l8kgejo#3 + Wed, 22 Jul 2026 16:54:33 GMT + + + + <![CDATA[Tankers make sharp U-turns after Houthi shipping threat]]> + + https://www.bbc.co.uk/news/articles/cn0n127lpzgo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cn0n127lpzgo#3 + Wed, 22 Jul 2026 14:42:49 GMT + + + + <![CDATA[Former defence minister Al Carns turns down ministerial offer]]> + + https://www.bbc.co.uk/news/articles/c74geex0k82o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c74geex0k82o#3 + Wed, 22 Jul 2026 22:12:25 GMT + + + + <![CDATA[Teenager drops social media addiction lawsuit against Meta]]> + + https://www.bbc.co.uk/news/articles/c5yrdg4q9vgo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/c5yrdg4q9vgo#3 + Wed, 22 Jul 2026 19:04:47 GMT + + + + <![CDATA[British woman jailed for blackmail after accusing banker of rape in Hong Kong]]> + + https://www.bbc.co.uk/news/articles/cz97gdjgezno?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/articles/cz97gdjgezno#3 + Wed, 22 Jul 2026 14:14:16 GMT + + + + <![CDATA[BBC News app]]> + + https://www.bbc.co.uk/news/10628994?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/news/10628994#4 + Wed, 30 Apr 2025 14:04:28 GMT + + + + <![CDATA[The Global Story: Is the Iran war back on?]]> + + https://www.bbc.co.uk/sounds/play/w3ct8ml2?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sounds/play/w3ct8ml2#5 + Wed, 22 Jul 2026 09:00:00 GMT + + + + <![CDATA[Who is the new PM?]]> + + https://www.bbc.co.uk/iplayer/episode/m002zlct?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/iplayer/episode/m002zlct#5 + Mon, 20 Jul 2026 14:00:00 GMT + + + + <![CDATA[Christopher Nolan's epic biopic starring Cillian Murphy]]> + + https://www.bbc.co.uk/iplayer/episode/m002p1fr?at_mid=8PiucSontB&at_campaign=Oppenheimer&at_medium=display_ad&at_campaign_type=owned&at_nation=NET&at_audience_id=SS&at_product=iplayer&at_brand=m002p1fr&at_ptr_name=bbc&at_ptr_type=media&at_format=image&at_objective=consumption&at_link_title=Oppenheimer&at_bbc_team=BBC&at_creation=Film + https://www.bbc.co.uk/iplayer/episode/m002p1fr?at_mid=8PiucSontB&at_campaign=Oppenheimer&at_medium=display_ad&at_campaign_type=owned&at_nation=NET&at_audience_id=SS&at_product=iplayer&at_brand=m002p1fr&at_ptr_name=bbc&at_ptr_type=media&at_format=image&at_objective=consumption&at_link_title=Oppenheimer&at_bbc_team=BBC&at_creation=Film#6 + Sun, 12 Jul 2026 23:50:47 GMT + + + + <![CDATA[Garnacho, Rogers and two clubs trying to balance the books]]> + + https://www.bbc.co.uk/sport/football/articles/cvg0723e0jko?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/football/articles/cvg0723e0jko#7 + Wed, 22 Jul 2026 15:54:29 GMT + + + + <![CDATA[Joshua not ready for death of friends to 'sink in' ]]> + + https://www.bbc.co.uk/sport/boxing/articles/czjl3em4g74o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/boxing/articles/czjl3em4g74o#7 + Wed, 22 Jul 2026 22:20:53 GMT + + + + <![CDATA[Saliba to miss extended period with back injury]]> + + https://www.bbc.co.uk/sport/football/articles/c5yveezg9q3o?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/football/articles/c5yveezg9q3o#7 + Wed, 22 Jul 2026 20:33:58 GMT + + + + <![CDATA[Menzies taken ill on stage at World Matchplay ]]> + + https://www.bbc.co.uk/sport/darts/articles/cqjxv0v1dlpo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/darts/articles/cqjxv0v1dlpo#7 + Wed, 22 Jul 2026 22:14:54 GMT + + + + <![CDATA[Inter Miami under investigation after signing Casemiro]]> + + https://www.bbc.co.uk/sport/football/articles/c3304ex7n7xo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/football/articles/c3304ex7n7xo#7 + Wed, 22 Jul 2026 20:44:04 GMT + + + + <![CDATA[Arokodare forces Wolves training to be cancelled]]> + + https://www.bbc.co.uk/sport/football/articles/cr5934j1jmdo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/football/articles/cr5934j1jmdo#7 + Wed, 22 Jul 2026 22:20:48 GMT + + + + <![CDATA[Miami mistakenly post James 'introductory' video]]> + + https://www.bbc.co.uk/sport/basketball/articles/c4gjwwny0zgo?at_medium=RSS&at_campaign=rss + https://www.bbc.co.uk/sport/basketball/articles/c4gjwwny0zgo#7 + Wed, 22 Jul 2026 20:54:08 GMT + + + + \ No newline at end of file diff --git a/tests/corpus/rss/bbc_news/expect.json b/tests/corpus/rss/bbc_news/expect.json new file mode 100644 index 0000000..2a23e84 --- /dev/null +++ b/tests/corpus/rss/bbc_news/expect.json @@ -0,0 +1,7 @@ +{ + "type": "rss", + "version": "2.0", + "title": "BBC News", + "items": 36, + "first_item_title": "PC Harper's widow criticises early prisoner release plan as Burnham to review scheme" +} diff --git a/tests/corpus/rss/heise/data.xml b/tests/corpus/rss/heise/data.xml new file mode 100644 index 0000000..3d71b60 --- /dev/null +++ b/tests/corpus/rss/heise/data.xml @@ -0,0 +1,1699 @@ + + + + heise online News + https://www.heise.de/ + Nachrichten nicht nur aus der Welt der Computer + Wed, 22 Jul 2026 22:42:00 +0200 + de + + + + O2 Telefónica streicht ein Sechstel aller Arbeitsplätze + https://www.heise.de/news/O2-Telefonica-streicht-ein-Sechstel-aller-Arbeitsplaetze-11374266.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374266 + Wed, 22 Jul 2026 22:42:00 +0200 + +

O2 schließt 60 Läden und streicht 1.100 Stellen. Goldene Handshakes sollen für freiwillige Kündigungen sorgen. Nächstes Jahr gibt es eine zweite Sparrunde.

]]>
+ +
+ + EU-Kommission genehmigt Paramount-Warner-Übernahme mit einer Auflage + https://www.heise.de/news/EU-Kommission-genehmigt-Paramount-Warner-Uebernahme-mit-einer-Auflage-11374234.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374234 + Wed, 22 Jul 2026 21:29:00 +0200 + +

Paramount Skydance darf im Europäischen Wirtschaftsraum den Konkurrenten Warner Bros. Discovery übernehmen. Nur im Kino-Segment bedarf es einer Zusage.

]]>
+ +
+ + Samsung prüft angeblich Milliarden-Einstieg beim KI-Start-up Mistral + https://www.heise.de/news/Samsung-prueft-angeblich-Milliarden-Einstieg-beim-KI-Start-up-Mistral-11374162.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374162 + Wed, 22 Jul 2026 20:43:00 +0200 + +

Samsung prüft laut Financial Times, sich am französischen KI-Start-up Mistral zu beteiligen. Dieses kann Speicherchips brauchen, die Samsung herstellt.

]]>
+ +
+ + Top 10: Das beste Gaming-Headset – Razer Blackshark V3 Pro ist Testsieger + https://www.heise.de/bestenlisten/testsieger/top-10-das-beste-gaming-headset-razer-blackshark-v3-pro-ist-testsieger/fxcd43c?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374080 + Wed, 22 Jul 2026 20:00:00 +0200 + + Ein gutes Gaming-Headset sollte die Spielsession bereichern, egal ob bei kompetitiven Shootern oder immersiven Rollenspielen. Wir zeigen die besten Modelle.

]]>
+ +
+ + Ubuntu: Enterprise Store verteilt Snaps ohne direkten Internetzugang + https://www.heise.de/news/Ubuntu-Enterprise-Store-verteilt-Snaps-ohne-direkten-Internetzugang-11373918.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373918 + Wed, 22 Jul 2026 19:33:00 +0200 + +

Canonical bringt einen Enterprise Store: Hierbei handelt es sich um einen Proxy für die lokale Verteilung von Snap-Paketen in Unternehmensnetzen.

]]>
+ +
+ + Milliarden-Deal: Anthropic setzt auf AMD-GPUs für KI-Rechenzentren + https://www.heise.de/news/Anthropic-will-ueber-eine-Million-AMD-GPUs-kaufen-11374084.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374084 + Wed, 22 Jul 2026 19:22:00 +0200 + +

Anthropic setzt ab 2027 AMDs Instinct-MI455X-Beschleuniger ein. Bis zu zwei Gigawatt Kapazität sind geplant.

]]>
+ +
+ + Chatkontrolle: EU-Länder sind bei Verlängerung einig + https://www.heise.de/news/Chatkontrolle-EU-Laender-sind-bei-Verlaengerung-einig-11374168.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374168 + Wed, 22 Jul 2026 19:13:00 +0200 + +

Der Verlängerung der Ausnahme für die Chatkontrolle steht nach einer Übereinkunft der EU-Länder nichts mehr im Weg – samt Änderungswünschen des Parlaments.

]]>
+ +
+ + Schwerpunkt Rüstung: Regierung legt Start-up-Strategie vor + https://www.heise.de/news/Schwerpunkt-Ruestung-Regierung-legt-Start-up-Strategie-vor-11374138.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374138 + Wed, 22 Jul 2026 18:42:00 +0200 + +

Weniger Bürokratie, mehr Kapital: Die Regierung will Innovationen in Deutschland halten und Gründungen aus der Wissenschaft erleichtern.

]]>
+ +
+ + Telekom muss Leerrohre fünf Jahre lang zu festen Konditionen öffnen + https://www.heise.de/news/Telekom-muss-Leerrohre-fuenf-Jahre-lang-zu-festen-Konditionen-oeffnen-11374096.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11374096 + Wed, 22 Jul 2026 17:47:00 +0200 + +

Die Bundesnetzagentur hat die vertraglichen Zugangsbedingungen für Kabelkanäle und Masten der Telekom für Wettbewerber zum Glasfaserausbau final festgelegt.

]]>
+ +
+ + heise+ | Speicherfehler aufspüren: Kostenlose Testprogramme für RAM + https://www.heise.de/ratgeber/Speicherfehler-aufspueren-Kostenlose-Testprogramme-fuer-RAM-11359860.html?wt_mc=rss.red.ho.ho.rdf.beitrag_plus.beitrag_plus + + + http://heise.de/-11359860 + Wed, 22 Jul 2026 17:30:00 +0200 + +

Defekte Speichermodule verursachen Abstürze und beschädigen Daten, sind aber schwer zu diagnostizieren. Wir geben Tipps, welche PC-Software RAM-Fehler aufdeckt.

]]>
+ +
+ + Astronomie: Erster plausibler Hinweis auf einen „supermerkwürdigen“ Exomond + https://www.heise.de/news/Astronomie-Erster-plausibler-Hinweis-auf-einen-supermerkwuerdigen-Exomond-11371953.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371953 + Wed, 22 Jul 2026 17:00:00 +0200 + +

Mehrfach gab es Hinweise auf Monde in einem anderen Sternsystem, bestätigt wurde keiner. Die bislang besten Indizien weisen nun auf ein merkwürdiges Exemplar.

]]>
+ +
+ + Störung bei Instagram + https://www.heise.de/news/Stoerung-bei-Instagram-11373999.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373999 + Wed, 22 Jul 2026 16:17:00 +0200 + +

Vor allem Chats bei Instagram funktionieren derzeit nur bedingt. Alte Nachrichten laden teilweise nicht, neue werden nicht verschickt.

]]>
+ +
+ + E-Scooter Kukirin G2 Pro ABE im Test: großer Akku, weiche Vollfederung, 450 Euro + https://www.heise.de/bestenlisten/testbericht/e-scooter-kukirin-g2-pro-abe-im-test-grosser-akku-weiche-vollfederung-450-euro/t6pz2qd?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373914 + Wed, 22 Jul 2026 16:00:00 +0200 + + Der E-Scooter Kukirin G2 Pro ABE will im Test mit Vollfederung, dickem Offroad-Profil und einem riesigen 748-Wh-Akku für kleines Geld überzeugen.

]]>
+ +
+ + Firefox 153 trennt Arbeits- und Privatkonten im selben Fenster + https://www.heise.de/news/Firefox-153-Native-Container-fuer-mehr-Privatsphaere-11373697.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373697 + Wed, 22 Jul 2026 15:46:00 +0200 + +

Firefox 153 führt native Container ein, trennt Browser-Kontexte und verbessert den Schutz lokaler Daten sowie die Privatsphäre.

]]>
+ +
+ + Cloud-Hyperscaler nehmen Nvidias Vera Rubin NVL72 in Betrieb + https://www.heise.de/news/Cloud-Hyperscaler-nehmen-Nvidias-Vera-Rubin-NVL72-in-Betrieb-11373705.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373705 + Wed, 22 Jul 2026 15:26:00 +0200 + +

Nvidia will AMDs Helios-Server zuvorkommen und verkündet den Einsatz eigener Vera-Rubin-Racks. Benchmarks sind mit Vorsicht zu genießen.

]]>
+ +
+ + USA und China planen angeblich KI-Gespräche im September + https://www.heise.de/news/USA-und-China-planen-angeblich-KI-Gespraeche-im-September-11373351.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373351 + Wed, 22 Jul 2026 15:22:00 +0200 + +

Die USA und China wollen im September wohl über gemeinsame KI-Regeln sprechen. US-Finanzminister Bessent soll die Gespräche leiten.

]]>
+ +
+ + Rechenzentren mit eigener Hitze kühlen: Adsorptionskühlung mit neuen Materialien + https://www.heise.de/news/Rechenzentren-mit-eigener-Hitze-kuehlen-Adsorptionskuehlung-mit-neuen-Materialien-11373567.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373567 + Wed, 22 Jul 2026 15:08:00 +0200 + +

Metallorganische Gerüstverbindungen (MOF) sollen Sorptionskühlung schon bei den relativ niedrigen Kühlwassertemperaturen von Rechenzentren ermöglichen.

]]>
+ +
+ + Smart Glasses von Samsung und Google starten zunächst außerhalb der EU + https://www.heise.de/news/Smart-Glasses-von-Samsung-und-Google-starten-zunaechst-ausserhalb-der-EU-11373709.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373709 + Wed, 22 Jul 2026 15:06:00 +0200 + +

Auf der Galaxy Unpacked zeigt Samsung zwei neue Designs seiner mit Google entwickelten smarten Brillen. Der Marktstart im Herbst erfolgt zunächst ohne EU-Raum.

]]>
+ +
+ + heise+ | Spiele-Engines: Warum viele erfolgreiche Studios auf Unity setzen + https://www.heise.de/hintergrund/Spiele-Engines-Warum-viele-erfolgreiche-Studios-auf-Unity-setzen-11301340.html?wt_mc=rss.red.ho.ho.rdf.beitrag_plus.beitrag_plus + + + http://heise.de/-11301340 + Wed, 22 Jul 2026 15:00:00 +0200 + +

Unity, Unreal, Godot oder was Eigenes? Viele Studios setzen bei der Spieleentwicklung auf Unity und sind damit erfolgreich. Wir haben nach den Gründen gefragt.

]]>
+ +
+ + Galaxy Watch Ultra2: Samsung-Smartwatch wird stärker, flacher – und teurer + https://www.heise.de/news/Galaxy-Watch-Ultra2-Samsung-Smartwatch-wird-staerker-flacher-und-teurer-11369233.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11369233 + Wed, 22 Jul 2026 15:00:00 +0200 + +

Samsung legt die Galaxy Watch Ultra neu auf und schraubt dabei an den Specs wie am Preis.

]]>
+ +
+ + Samsung erweitert Foldable-Reihe um breites Galaxy Z Fold 8 und erhöht Preise + https://www.heise.de/news/Samsung-erweitert-Foldable-Reihe-um-breites-Galaxy-Z-Fold-8-und-erhoeht-Preise-11372581.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372581 + Wed, 22 Jul 2026 15:00:00 +0200 + +

Samsung präsentiert neue Foldables: Das Galaxy Z Fold 8 kommt in einem kompakteren Formfaktor, begleitet von einem Ultra-Modell und einem leichteren Flip 8.

]]>
+ +
+ + VW: 2027 sollen in China Fahrzeuge mit Level-3-Fahren auf den Markt kommen + https://www.heise.de/news/VW-beschleunigt-Entwicklung-von-automatisiertem-Fahren-in-China-11373651.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373651 + Wed, 22 Jul 2026 14:44:00 +0200 + +

VW will die Entwicklung von Fahren mit Level 3 und Level 4 in China beschleunigen. Dafür vertieft der Konzern die Zusammenarbeit mit Horizon Robotics.

]]>
+ +
+ + Olimex erweitert Raspberry Pi Pico um Relais und Hochspannungseingänge + https://www.heise.de/news/Olimex-erweitert-Raspberry-Pi-Pico-um-Relais-und-Hochspannungseingaenge-11373546.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373546 + Wed, 22 Jul 2026 14:39:00 +0200 + +

Mit vier Relais und Eingängen bis 220 Volt bringt das PICO-EVB Industrie-I/O auf den Raspberry Pi Pico.

]]>
+ +
+ + Container-Images ohne CVEs: BellSofts neuer Buildpacks-Builder + https://www.heise.de/news/Container-Images-ohne-CVEs-BellSofts-neuer-Buildpacks-Builder-11373261.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373261 + Wed, 22 Jul 2026 14:38:00 +0200 + +

BellSofts gehärteter Builder für Paketo Buildpacks baut Container-Images auf einer weitgehend CVE-freien Alpaquita-Basis – ganz ohne Dockerfile.

]]>
+ +
+ + Astronomie: Nächster Hinweis auf Verbleib der fehlenden Materie im Universum + https://www.heise.de/news/Astronomie-Naechster-Hinweis-auf-Verbleib-der-fehlenden-Materie-im-Universum-11373205.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373205 + Wed, 22 Jul 2026 14:36:00 +0200 + +

Zwar macht herkömmliche Materie nur einen Bruchteil des Kosmos aus, trotzdem kennen wir nicht einmal deren Vorkommen komplett. Nun gibt es einen neuen Hinweis.

]]>
+ +
+ + Windows: Global Device ID führt zu gerichtswirksamer Identifikation + https://www.heise.de/news/Windows-Global-Device-ID-fuehrt-zu-gerichtswirksamer-Identifikation-11373417.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373417 + Wed, 22 Jul 2026 14:10:00 +0200 + +

In einem Gerichtsprozess in den USA stellte sich heraus, dass eine Global Device ID von Windows Nutzer identifizierbar macht.

]]>
+ +
+ + heise-Angebot: iX-Workshop: Aufgaben eines Informationssicherheitsbeauftragten + https://www.heise.de/news/iX-Workshop-Aufgaben-eines-Informationssicherheitsbeauftragten-11362170.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11362170 + Wed, 22 Jul 2026 14:00:00 +0200 + +

Sich mit der Rolle des Informationssicherheitsbeauftragten vertraut machen: die Anforderungen verstehen und die notwendigen Kompetenzen erwerben.

]]>
+ +
+ + Kommentar: OpenAI – KI-Sicherheit nur ein Marketing-Gag? + https://www.heise.de/meinung/Kommentar-OpenAIs-KI-laeuft-Amok-so-oder-so-11373435.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373435 + Wed, 22 Jul 2026 13:51:00 +0200 + +

Eine KI bricht aus und übernimmt fremde Server. Droht uns jetzt das Ende der Menschheit? Falsche Frage, meint Jürgen Schmidt, Leiter von heise security.

]]>
+ +
+ + heise+ | Asus ExpertBook Ultra mit mattem OLED-Touchscreen und 64 GByte RAM im Test + https://www.heise.de/tests/Asus-ExpertBook-Ultra-mit-mattem-OLED-Touchscreen-und-64-GByte-RAM-im-Test-11350587.html?wt_mc=rss.red.ho.ho.rdf.beitrag_plus.beitrag_plus + + + http://heise.de/-11350587 + Wed, 22 Jul 2026 13:30:00 +0200 + +

Asus gönnt seinem leichten ExpertBook Ultra einen matten OLED-Touchscreen, einen leisen Lüfter und bis zu 64 GByte Arbeitsspeicher.

]]>
+ +
+ + macOS 27: Golden Gate kündigt DVD-Wiedergabe-Framework ab + https://www.heise.de/news/macOS-27-Golden-Gate-kuendigt-DVD-Wiedergabe-Framework-ab-11373313.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373313 + Wed, 22 Jul 2026 13:21:00 +0200 + +

Apple schießt demnächst ein Framework zur DVD-Wiedergabe ab. Als Nächstes ist dann die gesamte Wiedergabe dran. Doch es gibt Alternativen.

]]>
+ +
+ + Offene Modelle als Agenten: LM Studio bringt Bionic + https://www.heise.de/news/LM-Studio-Bionic-Lokales-KI-Agentensystem-fuer-offene-Modelle-11372183.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372183 + Wed, 22 Jul 2026 13:16:00 +0200 + +

LM Studio begibt sich ins Agentengeschäft. Mit Bionic lassen sich offene Modelle lokal oder via Cloud nutzen, um Mac oder PC zu steuern.

]]>
+ +
+ + Weltweit erstes Wellenkraftwerk erhält DNV-Zertifizierung + https://www.heise.de/news/Weltweit-erstes-Wellenkraftwerk-erhaelt-DNV-Zertifizierung-11373393.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373393 + Wed, 22 Jul 2026 13:00:00 +0200 + +

Die norwegische Klassifizierungsgesellschaft DNV hat das Wellenkraftwerk Corpower C4 zertifiziert. Es ist die erste Zertifizierung für eine solche Anlage.

]]>
+ +
+ + Luftfahrtunternehmen wollen Gehäuse für F-35-Kampfjet-Triebwerk 3D-drucken + https://www.heise.de/news/Luftfahrtunternehmen-wollen-Gehaeuse-fuer-F-35-Kampfjet-Triebwerk-3D-drucken-11373285.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373285 + Wed, 22 Jul 2026 12:27:00 +0200 + +

Pratt & Witney und GKN Aerospace wollen strukturelle Bauteile von Militärflugzeugen per 3D-Druck erstellen und das anhand eines Triebwerksgehäuses erproben.

]]>
+ +
+ + Reiche: Keine neuen Entlastungen bei Spritpreisen geplant + https://www.heise.de/news/Reiche-Keine-neuen-Entlastungen-bei-Spritpreisen-geplant-11373317.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373317 + Wed, 22 Jul 2026 12:18:00 +0200 + +

Bundeswirtschaftsministerin Reiche sieht trotz gestiegener Spritpreise keinen Spielraum für neue Entlastungen. Die Haushaltslage lasse das nicht zu.

]]>
+ +
+ + heise-Angebot: Last Call: M365 für Admins – Sichere Konfiguration & effiziente Verwaltung + https://www.heise.de/news/Last-Call-M365-fuer-Admins-Sichere-Konfiguration-effiziente-Verwaltung-11338965.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11338965 + Wed, 22 Jul 2026 12:00:00 +0200 + +

Perfekter Einstieg in die Verwaltung von Microsoft 365: IT-Admins erhalten einen detaillierten Überblick – von Lizenzen über Tenant bis Purview.

]]>
+ +
+ + Apples „E-Mail-Adresse verbergen“: Sammelklage läuft trotz Bugfix weiter + https://www.heise.de/news/Apples-E-Mail-Adresse-verbergen-Sammelklage-laeuft-trotz-Bugfix-weiter-11372179.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372179 + Wed, 22 Jul 2026 10:56:00 +0200 + +

Apples E-Mail-Schutz hatte seit einem Jahr einen Bug, der Daten-Leaks erlaubte. Inzwischen hat sich Apple erbarmt, doch eine US-Sammelklage wird fortgesetzt.

]]>
+ +
+ + Australien: Die „Coolen“ ignorieren Social-Media-Sperre, viele kommen zurück + https://www.heise.de/news/Australien-Die-Coolen-ignorieren-Social-Media-Sperre-viele-kommen-zurueck-11373062.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373062 + Wed, 22 Jul 2026 10:37:00 +0200 + +

Laut einer Umfrage wächst der Anteil der Kinder und Jugendlichen, die Australiens Social-Media-Sperre umgehen. Wer sich daran hält, gilt eher als „uncool“.

]]>
+ +
+ + „Live Notes“: Apple will von Support-Gesprächen im Store lernen + https://www.heise.de/news/Live-Notes-Apple-will-von-Support-Gespraechen-im-Store-lernen-11372167.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372167 + Wed, 22 Jul 2026 10:20:00 +0200 + +

Apple testet ein KI-System, um seinen Kundendienstmitarbeitern zu helfen. „Live Notes“ erfasst Gespräche – und manche Supporter fürchtet Überwachung.

]]>
+ +
+ + Datentransfersoftware Serv-U hat 15 kritische Sicherheitslücken + https://www.heise.de/news/Datentransfersoftware-Serv-U-hat-15-kritische-Sicherheitsluecken-11373098.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373098 + Wed, 22 Jul 2026 10:20:00 +0200 + +

In Serv-U schließt SolarWinds mit einem Update diverse kritische Lücken, die etwa das Einschleusen von Schadcode erlauben.

]]>
+ +
+ + Deutsches Robotik-Start-up sammelt größte Seed-Runde ein – Koop mit Google Cloud + https://www.heise.de/news/Deutsches-Robotik-Start-up-sammelt-groesste-Seed-Runde-ein-Koop-mit-Google-Cloud-11371216.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371216 + Wed, 22 Jul 2026 10:02:00 +0200 + +

Mit 48 Millionen Euro hat microagi die größte Einstiegsfinanzierung eines deutschen Start-ups eingesammelt. Google Cloud wird erster Partner mit Nvidia-Technik.

]]>
+ +
+ + heise-Angebot: iX-Workshop: Microsoft 365 Copilot für IT-Administratoren + https://www.heise.de/news/iX-Workshop-Microsoft-365-Copilot-fuer-IT-Administratoren-11362164.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11362164 + Wed, 22 Jul 2026 10:00:00 +0200 + +

Lernen Sie, wie Sie Microsoft 365 Copilot sicher und datenschutzkonform implementieren. +

]]>
+ +
+ + Griechenland erlaubt E-Scooter-Fahren nur noch mit Helm und erst ab 17 Jahren + https://www.heise.de/news/Griechenland-erlaubt-E-Scooter-Fahren-nur-noch-mit-Helm-und-erst-ab-17-Jahren-11373072.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11373072 + Wed, 22 Jul 2026 09:57:00 +0200 + +

Griechenland reagiert mit Helmpflicht, Versicherung und hohen Bußgeldern auf die Häufung von E-Tretroller-Unfällen.

]]>
+ +
+ + Mental Health: Viele wissenschaftlich fundierte Apps nicht mehr verfügbar + https://www.heise.de/news/Mental-Health-Viele-wissenschaftlich-fundierte-Apps-nicht-mehr-verfuegbar-11372733.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372733 + Wed, 22 Jul 2026 09:54:00 +0200 + +

Eine Übersichtsarbeit zeigt, dass nur etwa die Hälfte aller Mental Health Apps mit wissenschaftlich geprüfter Wirksamkeit noch öffentlich verfügbar ist.

]]>
+ +
+ + Pkw-Dichte in Deutschland erreicht mit 593 Autos je 1000 Einwohner neuen Rekord + https://www.heise.de/news/Pkw-Dichte-in-Deutschland-erreicht-mit-593-Autos-je-1000-Einwohner-neuen-Rekord-11372967.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372967 + Wed, 22 Jul 2026 09:45:00 +0200 + +

Zum Jahresanfang 2026 kamen in Deutschland 593 Pkw auf 1000 Einwohner. Der Zuwachs speist sich vor allem aus dem E-Auto-Hochlauf.

]]>
+ +
+ + Backupsoftware Veeam: Updater ermöglicht Rechteausweitung + https://www.heise.de/news/Backupsoftware-Veeam-Updater-ermoeglicht-Rechteausweitung-11372985.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372985 + Wed, 22 Jul 2026 09:37:00 +0200 + +

IT-Sicherheitsforscher haben in der Update-Komponente von Veeam eine Sicherheitslücke entdeckt, die das Ausweiten der Rechte ermöglicht.

]]>
+ +
+ + Solarfenster nutzt Sonnenlicht von außen und Kunstlicht von innen + https://www.heise.de/news/Solarfenster-nutzt-Sonnenlicht-von-aussen-und-Kunstlicht-von-innen-11372927.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372927 + Wed, 22 Jul 2026 09:23:00 +0200 + +

Fensterflächen können zur Energiegewinnung genutzt werden, wie ein Prototyp eines halbtransparenten Perowskit-Solarmoduls zeigt.

]]>
+ +
+ + „Passwort“ Folge 62: IETF-Grabenkämpfe, Umbrella-CVEs, Riksha-Hacking und mehr + https://www.heise.de/news/Passwort-Folge-62-IETF-Grabenkaempfe-Umbrella-CVEs-Riksha-Hacking-und-mehr-11360955.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11360955 + Wed, 22 Jul 2026 09:00:00 +0200 + +

Eine Podcastfolge fast ohne PKI, aber mit gehörig Drama (bei der IETF), Kopfschütteln (über Cisco) und gefährlichen Sicherheitslücken (im In- und Ausland).

]]>
+ +
+ + Urlaubsplanung mit KI: Zwischen Geheimtipp und Fehlplanung + https://www.heise.de/news/Von-Koalas-und-Kobolden-Wenn-KI-den-Urlaub-plant-11372869.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372869 + Wed, 22 Jul 2026 08:42:00 +0200 + +

KI-Assistenten erobern die Urlaubswelt. Die digitalen Helfer sind immer höflich und meistens hilfreich – aber manchmal auch herrlich daneben.

]]>
+ +
+ + Oracle Critical Patch Update: 1449 Softwareflicken im Juli + https://www.heise.de/news/Oracle-Critical-Patch-Update-1449-Softwareflicken-im-Juli-11372857.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372857 + Wed, 22 Jul 2026 08:30:00 +0200 + +

Zum vierteljährlichen Oracle CPU liefert der Hersteller 1449 Sicherheitsupdates aus – ein neuer Rekordwert. Admins sollten handeln.

]]>
+ +
+ + Freelancer: SAP- und ERP-Spezialisten trotzen Abwärtstrend + https://www.heise.de/news/Freelancer-SAP-und-ERP-Spezialisten-trotzen-Abwaertstrend-11372517.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372517 + Wed, 22 Jul 2026 08:10:00 +0200 + +

SAP- und ERP-Spezialisten sind laut einer Auswertung die Top-Verdiener unter den IT-Freiberuflern. Insgesamt wird die Auftragslage aber schlechter.

]]>
+ +
+ + Neue Eskalation im Nahen Osten: Iran hat AWS-Infrastruktur in Bahrain „zerstört“ + https://www.heise.de/news/Neue-Eskalation-im-Nahen-Osten-Iran-hat-AWS-Infrastruktur-in-Bahrain-zerstoert-11372831.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372831 + Wed, 22 Jul 2026 08:01:00 +0200 + +

Die USA fliegen seit Tagen wieder Angriffe auf den Iran, das Land attackiert seine Nachbarn. Angeblich wurden dabei jetzt AWS-Rechenzentren in Bahrain zerstört.

]]>
+ +
+ + heise-Angebot: c’t KI-Wissen 2026: So entkommen Sie dem Räderwerk der KI + https://www.heise.de/news/c-t-KI-Wissen-2026-So-entkommen-Sie-dem-Raederwerk-der-KI-11372069.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372069 + Wed, 22 Jul 2026 08:00:00 +0200 + +

KI verdichtet Arbeitsprozesse und erzeugt mehr Stress. Das neue Sonderheft c’t KI-Wissen 2026 erklärt Ursachen und zeigt Auswege auf.

]]>
+ +
+ + KI-Modelle von OpenAI steckten hinter IT-Angriff auf Hugging Face + https://www.heise.de/news/KI-Modelle-von-OpenAI-haben-Cyberangriff-auf-Hugging-Face-autonom-ausgefuehrt-11372797.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372797 + Wed, 22 Jul 2026 07:11:00 +0200 + +

Bei einem Test der Fähigkeiten neuer KI-Modelle sind die aus ihrer Umgebung ausgebrochen und haben Hugging Face kompromittiert. Das hat OpenAI eingestanden.

]]>
+ +
+ + heise+ | Luxustourer Yamaha Tracer 9 GT+ mit Halbautomatik im Test + https://www.heise.de/tests/Motorrad-Yamaha-Tracer-9-GT-im-Test-Komfortables-Reisen-auf-hohem-Niveau-11369271.html?wt_mc=rss.red.ho.ho.rdf.beitrag_plus.beitrag_plus + + + http://heise.de/-11369271 + Wed, 22 Jul 2026 07:00:00 +0200 + +

Yamaha kombiniert einen drehfreudigen Dreizylinder mit einem Halbautomatik-Getriebe. Das Tourenmotorrad überzeugt im Test, auch durch sein Fahrwerk.

]]>
+ +
+ + Mittwoch: Googles neue Lite-KI ohne Pro, Rechenzentren als zu hohe Stromfresser + https://www.heise.de/news/Mittwoch-Googles-neue-Lite-KI-ohne-Pro-Rechenzentren-als-zu-hohe-Stromfresser-11372781.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372781 + Wed, 22 Jul 2026 06:15:00 +0200 + +

Gemini-Neuerungen nur Lite + Energiebedarf von Rechenzentren + Apples Hide my Email unzureichend + EU-Reform gegen Informationsfreiheit + Entspannung bei SSDs

]]>
+ +
+ + Studie: US-Rechenzentren werden 2035 ein Fünftel des Landesstroms verbrauchen + https://www.heise.de/news/Studie-US-Rechenzentren-werden-2035-ein-Fuenftel-des-Landesstroms-verbrauchen-11372767.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372767 + Wed, 22 Jul 2026 05:03:00 +0200 + +

Aktuell stellen Rechenzentren in den USA 5,9 Prozent des gesamten Stromverbrauchs dar. Das wird in den nächsten zehn Jahren massiv ansteigen, so eine Prognose.

]]>
+ +
+ + Google bringt Gemini 3.6 Flash und 3.5 Flash-Lite, hadert mit 3.5 Pro + https://www.heise.de/news/Google-bringt-Gemini-3-6-Flash-und-3-5-Flash-Lite-hadert-mit-3-5-Pro-11372719.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372719 + Tue, 21 Jul 2026 22:35:00 +0200 + +

Google legt neue Light-Varianten seines LLMs auf. Bei der mächtigeren Pro-Variante spießt es sich weiter.

]]>
+ +
+ + So einfach war Apples Hide my Email auszuhebeln + https://www.heise.de/news/Apple-korrigiert-Hide-my-Email-im-dritten-Anlauf-11372689.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372689 + Tue, 21 Jul 2026 21:36:00 +0200 + +

Apples Dienst „E-Mail-Adresse verbergen” schaffte das nur schlecht. Die echten Adressen aufzudecken, war simpel.

]]>
+ +
+ + Brüsseler Vorstoß: Reform soll Informationsfreiheit bei der Kommission schwächen + https://www.heise.de/news/Bruesseler-Vorstoss-Reform-soll-Informationsfreiheit-bei-der-Kommission-schwaechen-11372679.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372679 + Tue, 21 Jul 2026 20:17:00 +0200 + +

Ein EU-Kommissar fordert laut einem Leak eine „Perestroika“ der EU-Bürokratie, aber auch einen geschützten Raum für interne Debatten. Beobachter sind alarmiert.

]]>
+ +
+ + Top 10: Speicher für Balkonkraftwerk im Test + https://www.heise.de/bestenlisten/testsieger/top-10-speicher-fuer-balkonkraftwerk-im-test-zendure-vor-anker/9g7b03h?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372571 + Tue, 21 Jul 2026 20:00:00 +0200 + + Welcher Speicher fürs Balkonkraftwerk lohnt sich? Wir zeigen die besten Modelle – der Testsieger funktioniert ohne Cloud.

]]>
+ +
+ + „Milliarden-Abkommen“: Microsoft und Mistral vertiefen KI-Partnerschaft + https://www.heise.de/news/Milliarden-Abkommen-Microsoft-und-Mistral-vertiefen-KI-Partnerschaft-11372629.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372629 + Tue, 21 Jul 2026 19:16:00 +0200 + +

Microsoft und Mistral bauen ihre Partnerschaft deutlich aus. Das umfangreiche Abkommen umfasst KI-Infrastruktur, Modellintegration und Vertrieb.

]]>
+ +
+ + Stellantis will Modelle mit Schwarmintelligenz von Mobileye ausstatten + https://www.heise.de/news/Stellantis-will-Modelle-mit-Schwarmintelligenz-von-Mobileye-ausstatten-11372625.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372625 + Tue, 21 Jul 2026 19:01:00 +0200 + +

Mobileye lässt Autos Straßendaten sammeln und stellt diese dann wieder zur Verfügung. Stellantis will einige Modelle mit dem System ausstatten.

]]>
+ +
+ + Google plant "Frozen v2": KI-Chips mit fest integrieren Gemini-Eigenschaften + https://www.heise.de/news/Google-plant-KI-Chips-mit-integrierten-Gemini-Strukturen-11372439.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372439 + Tue, 21 Jul 2026 18:52:00 +0200 + +

Google plant Chips, in die Gemini-Modellstrukturen fest integriert sein sollen. Sie sollen KI-Anwendungen schneller und effektiver machen.

]]>
+ +
+ + Darpa und US-Luftwaffe rüsten F-16 zu autonomen Jets um + https://www.heise.de/news/Darpa-und-US-Luftwaffe-ruesten-F-16-zu-autonomen-Jets-um-11372501.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372501 + Tue, 21 Jul 2026 17:05:00 +0200 + +

In Florida testen die US-Luftwaffe und die Darpa gerade autonom fliegende Kampfjets. Dabei handelt es sich um modifizierte F-16.

]]>
+ +
+ + Vorverkauf für VW Golf und VW T-Roc Vollhybrid mit 125 kW beginnt + https://www.heise.de/news/Vorverkauf-fuer-VW-Golf-und-VW-T-Roc-Vollhybrid-mit-125-kW-beginnt-11372259.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372259 + Tue, 21 Jul 2026 16:41:00 +0200 + +

Der ab sofort bestellbare 125-kW-Vollhybridantrieb für VW Golf und VW T-Roc Hybrid rundet Volkswagens Hybrid-Palette zwischen den Mild- und Plug-in-Hybriden ab.

]]>
+ +
+ + Towertop SUP-Pumpe im Test: Starke Luftpumpe für Kajak, SUP und Mountainbike + https://www.heise.de/bestenlisten/testbericht/towertop-sup-pumpe-im-test-starke-luftpumpe-fuer-kajak-sup-und-mountainbike/kj4ygvh?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372389 + Tue, 21 Jul 2026 16:00:00 +0200 + + Die Towertop SUP-Pumpe mit Akku vereint kompakte Größe und hohen Maximaldruck. Ob die Hybrid-Pumpe für SUP, Reifen und Bälle überzeugt, zeigt unser Test.

]]>
+ +
+ + Schwarz Digits eröffnet seinen 3500-Mitarbeiter-Campus im Süden Deutschlands + https://www.heise.de/news/Schwarz-Digits-eroeffnet-seinen-3500-Mitarbeiter-Campus-im-Sueden-Deutschlands-11372342.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372342 + Tue, 21 Jul 2026 15:13:00 +0200 + +

Die neue Heimat für die IT-Sparte der Schwarz Gruppe steht. In Bad Friedrichshall finden Tausende Mitarbeiter Platz.

]]>
+ +
+ + Valve-Fanprojekt verwandelt Steam Machine in einen Companion Cube + https://www.heise.de/news/Valve-Fanprojekt-verwandelt-Steam-Machine-in-einen-Companion-Cube-11372336.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372336 + Tue, 21 Jul 2026 15:12:00 +0200 + +

Ein Valve-Fan hat kostenlose 3D-Druckdateien veröffentlicht, mit denen sich die Steam Machine in den ikonischen Companion Cube aus Portal kleiden lässt.

]]>
+ +
+ + S-Neo: Sparkassen kontern Neobroker + https://www.heise.de/news/S-Neo-Sparkassen-kontern-Neobrokern-11372284.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372284 + Tue, 21 Jul 2026 14:40:00 +0200 + +

Die Sparkassen bieten in ihrer Sparkassen-App mit S-Neo ein kostenloses Wertpapier-Depot an. Es macht Neobrokern Konkurrenz.

]]>
+ +
+ + WordPress-Lücke „wp2shell“ wird angegriffen + https://www.heise.de/news/WordPress-Luecke-wp2shell-wird-angegriffen-11372213.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372213 + Tue, 21 Jul 2026 14:13:00 +0200 + +

Zum Wochenende wurde die „wp2shell“-WordPress-Lücke bekannt. Seitdem beobachten IT-Sicherheitsforscher Attacken im Internet darauf.

]]>
+ +
+ + Garmin Cirqa: Bildschirmloser Fitnesstracker ohne Abo + https://www.heise.de/news/Garmin-Cirqa-Bildschirmloser-Fitnesstracker-ohne-Abo-11372241.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372241 + Tue, 21 Jul 2026 14:08:00 +0200 + +

Garmin hat mit dem Cirqa seinen ersten bildschirmlosen Fitnesstracker vorgestellt. Das Band erfasst Gesundheitsdaten und kommt ohne Abozwang.

]]>
+ +
+ + Exchange Online PowerShell: Mehr Zeit für die Authentifizierungs-Migration + https://www.heise.de/news/Exchange-Online-PowerShell-Mehr-Zeit-fuer-die-Authentifizierungs-Migration-11372051.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372051 + Tue, 21 Jul 2026 14:04:00 +0200 + +

Microsoft verschiebt die Entfernung des -Credential-Parameters in Exchange Online PowerShell: Administratoren sollten bis Dezember 2026 ihre Skripte anpassen.

]]>
+ +
+ + heise-Angebot: iX-Workshop Advanced Kubernetes Security: Effektive Maßnahmen und Best Practices + https://www.heise.de/news/iX-Workshop-Advanced-Kubernetes-Security-Effektive-Massnahmen-und-Best-Practices-11362158.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11362158 + Tue, 21 Jul 2026 14:00:00 +0200 + +

Lernen Sie anhand praktischer Beispiele, wie Sie Ihre Kubernetes-Umgebung durch effektive Methoden und Werkzeuge vor Angriffen schützen. +

]]>
+ +
+ + KI bricht aus Sandbox aus: OpenAI schlägt neue Art von Sicherheitsregeln vor + https://www.heise.de/news/KI-bricht-aus-Sandbox-aus-OpenAI-schlaegt-neue-Art-von-Sicherheitsregeln-vor-11371851.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371851 + Tue, 21 Jul 2026 13:49:00 +0200 + +

Ein KI-Modell von OpenAI hat wiederholt Wege gefunden, Sicherheitsmechanismen zu umgehen. Die Entwickler designen daher ein anderes Monitoring-System.

]]>
+ +
+ + Zahlen, bitte! Von nur noch 24.821 Sprechenden auf zum Wortschatz für Millionen + https://www.heise.de/hintergrund/Zahlen-bitte-Von-nur-noch-24-821-Sprechenden-auf-zum-Wortschatz-fuer-Millionen-11371917.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371917 + Tue, 21 Jul 2026 13:37:00 +0200 + +

Sanskrit ist eine Sprache mit einer großen Geschichte, die in Indien aber kaum noch gesprochen wird. Mithilfe von Onlinediensten und KI soll sich das ändern.

]]>
+ +
+ + Neuer Wiederherstellungsmodus: So funktioniert er in iOS 27 + https://www.heise.de/news/Neuer-Wiederherstellungsmodus-So-funktioniert-er-in-iOS-27-11370166.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370166 + Tue, 21 Jul 2026 13:22:00 +0200 + +

Apple ermöglicht es künftig, bestimmte Probleme am iPhone direkt zu überprüfen. So arbeitet der neue Modus.

]]>
+ +
+ + SSDs: Entspannung durch Sturzflug bei Notebooks und Smartphones absehbar + https://www.heise.de/news/SSDs-Speichermangel-soll-sich-2027-bessern-11372075.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372075 + Tue, 21 Jul 2026 13:17:00 +0200 + +

Ab der zweiten Jahreshälfte 2027 soll es wieder mehr NAND-Flash geben, als weltweit gebraucht wird – weil bei Endkunden die Nachfrage wegbricht.

]]>
+ +
+ + In Straße mit Gefahrenstelle eingefahren: Zoox ruft Robotaxis zurück + https://www.heise.de/news/In-Strasse-mit-Gefahrenstelle-eingefahren-Zoox-ruft-Robotaxis-zurueck-11372085.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372085 + Tue, 21 Jul 2026 12:57:00 +0200 + +

Weil ein Robotaxi Rauch ignoriert und sich einer Brandstelle genähert hat, ruft Zoox seine Fahrzeuge zurück. Laut einer Verkehrsbehörde war es kein Einzelfall.

]]>
+ +
+ + Daimler-Truck-Chefin erwartet chinesische E-Lkw-Offensive + https://www.heise.de/news/Daimler-Truck-Chefin-erwartet-chinesische-E-Lkw-Offensive-11372033.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11372033 + Tue, 21 Jul 2026 12:54:00 +0200 + +

Daimler-Truck-Chefin rechnet bei Elektro-Lkw mit kräftiger Konkurrenz aus China. Um wettbewerbsfähiger zu werden, sollen in Deutschland 5000 Jobs wegfallen.

]]>
+ +
+ + Neuerungen bei iPhone 18 Pro und 18 Pro Max: Was Apple plant + https://www.heise.de/news/iPhone-18-Pro-und-18-Pro-Max-Das-erwarten-wir-11370906.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370906 + Tue, 21 Jul 2026 12:50:00 +0200 + +

In gut zwei Monaten findet Apples nächste iPhone-Keynote statt. Für die Pro-Modelle plant Apple einige Verbesserungen. Die fünf wichtigsten im Überblick.

]]>
+ +
+ + Absprache zu Reifenpreisen - Kartellamt verhängt Geldbußen + https://www.heise.de/news/Absprache-zu-Reifenpreisen-Kartellamt-verhaengt-Geldbussen-11371947.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371947 + Tue, 21 Jul 2026 12:21:00 +0200 + +

Das Kartellamt sieht durch Preisvorgaben beim Reifengroßhandel die Preissetzungsfreiheit eingeschränkt. Die Wettbewerbshüter gehen gegen drei Firmen vor.

]]>
+ +
+ + heise-Angebot: heise devSec: Jetzt noch Frühbuchertickets für die Konferenz in Marburg sichern + https://www.heise.de/news/heise-devSec-Jetzt-noch-Fruehbuchertickets-fuer-die-Konferenz-in-Marburg-sichern-11370706.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370706 + Tue, 21 Jul 2026 12:00:00 +0200 + +

Die zehnte heise devSec behandelt im September Themen von Supply-Chain-Security über sichere KI-gestützte Softwareentwicklung bis zu Post-Quantum-Kryptologie.

]]>
+ +
+ + Nextcloud: Wirrwarr um Website-Störung – Cyberangriff bestätigt + https://www.heise.de/news/Nextcloud-Cyberangriff-auf-Webserver-mit-ungeschickter-Kommunikation-11371959.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371959 + Tue, 21 Jul 2026 11:55:00 +0200 + +

Am Sonntag ging die Webseite von Nextcloud vorübergehend offline. Der Anbieter bestätigt auf Anfrage einen Cyberangriff.

]]>
+ +
+ + Wettbewerbsverfahren: Apple beginnt Gespräche mit US-Justizministerium + https://www.heise.de/news/Wettbewerbsverfahren-Apple-beginnt-Gespraeche-mit-US-Justizministerium-11370162.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370162 + Tue, 21 Jul 2026 11:43:00 +0200 + +

Es geht um Apples iPhone-Ökosystem: 2024 reichte das Department of Justice Klage ein. Nun gibt es Bestrebungen zu einer Einigung – mit ungewissem Ausgang.

]]>
+ +
+ + Erstmals ist mehr als die Hälfte neu hochgeladener Songs auf Deezer KI + https://www.heise.de/news/Deezer-Mehrheit-hochgeladener-Musik-ist-jetzt-KI-generiert-11371907.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371907 + Tue, 21 Jul 2026 11:37:00 +0200 + +

Bei Deezer ist im Juni erstmals mehr als die Hälfte neu hochgeladener Musik KI-generiert – im Schnitt rund 90.000 Tracks pro Tag.

]]>
+ +
+ + Suno-Datenleck: Have I Been Pwned ergänzt 55 Millionen Konten + https://www.heise.de/news/Suno-Datenleck-Have-I-Been-Pwned-ergaenzt-55-Millionen-Konten-11371843.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371843 + Tue, 21 Jul 2026 11:33:00 +0200 + +

Das Have-I-Been-Pwned-Projekt hat mehr als 55 Millionen Konten aus dem Suno-Datenleck zur Datenhalde hinzugefügt.

]]>
+ +
+ + "KI-Kommunismus": Washington ringt um den Umgang mit chinesischer KI + https://www.heise.de/news/US-Regierung-prueft-Massnahmen-gegen-chinesische-KI-Modelle-11371855.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371855 + Tue, 21 Jul 2026 11:30:00 +0200 + +

Kimi K3 und andere chinesische Open-Weight-Modelle befeuern in Washington den Ruf nach politischen Gegenmaßnahmen. Auch im Netz wird hitzig diskutiert.

]]>
+ +
+ + Sicherheitspatch Grafana: Angreifer können sensible Daten abgreifen + https://www.heise.de/news/Sicherheitspatch-Grafana-Angreifer-koennen-sensible-Daten-abgreifen-11371859.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371859 + Tue, 21 Jul 2026 11:25:00 +0200 + +

Das Open-Source-Visualisierungs- und Monitoring-Tool Grafana ist verwundbar. Nun haben die Entwickler eine kritische Sicherheitslücke geschlossen.

]]>
+ +
+ + Bestes Raumklima: Taupunktlüfter Update auf Version 7.0. + https://www.heise.de/news/Bestes-Raumklima-Taupunktluefter-Update-auf-Version-7-0-11371715.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371715 + Tue, 21 Jul 2026 11:22:00 +0200 + +

Der Taupunktlüfter hat sich in den letzten Jahren als zuverlässiges System zum Trocknen von Wohnungen und Häusern erwiesen. Nun gibt es ein Update der Software.

]]>
+ +
+ + Gut versteckt: Verbotene Glücksspiele im brasilianischen App Store + https://www.heise.de/news/Gut-versteckt-Verbotene-Gluecksspiele-im-brasilianischen-App-Store-11370910.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370910 + Tue, 21 Jul 2026 11:15:00 +0200 + +

Entwicklern ist es offenbar gelungen, „dutzende“ Apps an Apples Review-Prozess vorbeizuschmuggeln, über die man auf Zock-Plattformen gelangt.

]]>
+ +
+ + Microsoft baut Outlook um: Copilot rein, Insights raus + https://www.heise.de/news/Microsoft-streicht-Meeting-Insights-aus-Outlook-11371664.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371664 + Tue, 21 Jul 2026 11:11:00 +0200 + +

Microsoft schaltet Meeting Insights in Outlook ab und ersetzt es durch Copilot. Auch beim E-Mail-Schreiben hilft Copilot künftig.

]]>
+ +
+ + Frankreich: Social-Media-Verbot für Unter-15-Jährige wohl nach den Sommerferien + https://www.heise.de/news/Frankreich-Social-Media-Verbot-fuer-Unter-15-Jaehrige-wohl-nach-den-Sommerferien-11371755.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371755 + Tue, 21 Jul 2026 10:55:00 +0200 + +

Ab September sollen Kinder in Frankreich keine Social-Media-Konten mehr anlegen können. Alte werden ab Januar gesperrt. Darauf hat sich das Parlament geeinigt.

]]>
+ +
+ + Qnetic baut wohl weltgrößte Testanlage für 200-kWh-Schwungrad-Energiespeicher + https://www.heise.de/news/Qnetic-baut-wohl-weltgroesste-Testanlage-fuer-200-kWh-Schwungrad-Energiespeicher-11371749.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371749 + Tue, 21 Jul 2026 10:51:00 +0200 + +

Qnetic baut in Shanghai eine Testanlage, um seine Schwungrad-Energiespeicher testen zu können. Ein 200-kWh-Speicher soll als erstes validiert werden.

]]>
+ +
+ + Von der Idee zur Spec: Das neue Feature in Spec Kit 0.13 + https://www.heise.de/news/Von-der-Idee-zur-Spec-Das-neue-Feature-in-Spec-Kit-0-13-11371623.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371623 + Tue, 21 Jul 2026 10:44:00 +0200 + +

Die neue Assess-Erweiterung von Spec Kit 0.13 sortiert automatisiert Ideen, bevor sie in eine Spec eingehen. Außerdem beseitigt Spec Kit eine Reihe an Bugs.

]]>
+ +
+ + Software Testing: Autismus im Software-Testing-Bereich + https://www.heise.de/blog/Software-Testing-Autismus-im-Software-Testing-Bereich-11371595.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371595 + Tue, 21 Jul 2026 10:13:00 +0200 + +

Wie Personen mit Autismusdiagnose ihre Fähigkeiten gut im Software-Testing einsetzen können, erklären Helmut Pichler und Markus Kalbhenn.

]]>
+ +
+ + TeamViewer: BaFin verhängt Bußgeld nach Cyberangriff + https://www.heise.de/news/TeamViewer-BaFin-verhaengt-Bussgeld-nach-Cyberangriff-11371639.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371639 + Tue, 21 Jul 2026 10:06:00 +0200 + +

Die Finanzaufsicht BaFin hat TeamViewer ein Bußgeld von 240.000 Euro aufgebrummt. Grund ist ein Cyberangriff aus dem Jahr 2024.

]]>
+ +
+ + heise-Angebot: iX-Workshop IT-Grundschutz-Praktiker mit Zertifikat + https://www.heise.de/news/iX-Workshop-IT-Grundschutz-Praktiker-mit-Zertifikat-11362222.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11362222 + Tue, 21 Jul 2026 10:00:00 +0200 + +

Erhalten Sie einen umfassenden Einblick in die Praxis der IT-Grundschutz-Methodik des BSI. Mit anschließender Prüfung und Zertifizierung.

]]>
+ +
+ + „Desktop Explorer“ angespielt: Denksport für Profis + https://www.heise.de/tests/Desktop-Explorer-angespielt-Denksport-fuer-Profis-11371670.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371670 + Tue, 21 Jul 2026 09:52:00 +0200 + +

„Desktop Explorer“ lässt Spieler die rätselhaften Geheimnisse eines alten Computers entdecken. Ein minimalistisches, aber extrem forderndes Mystery-Abenteuer.

]]>
+ +
+ + Passkeys in der Praxis – Teil 1: Die Architektur von Passkeys + https://www.heise.de/hintergrund/Passkeys-in-der-Praxis-Teil-1-Die-Architektur-von-Passkeys-11364345.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11364345 + Tue, 21 Jul 2026 09:10:00 +0200 + +

So funktionieren Passkeys: Der erste Teil der Praxis-Serie für Entwickler zeigt im Detail die Architektur, die auf FIDO2 und WebAuthn aufbaut.

]]>
+ +
+ + Coca Cola: Molkereisparte fairlife stellt Produktion nach Cyberangriff ein + https://www.heise.de/news/Coca-Cola-Molkereisparte-fairlife-stellt-Produktion-nach-Cyberangriff-ein-11371577.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371577 + Tue, 21 Jul 2026 09:09:00 +0200 + +

Der Molkereiprodukte-Zweig fairlife von Coca Cola muss nach einem Ransomware-Vorfall temporär die Produktion einstellen.

]]>
+ +
+ + DLSS 5: Nvidias neue Demo wirkt weit weniger drastisch als im März + https://www.heise.de/news/Neural-Rendering-Nvidia-zeigt-DLSS-5-deutlich-zurueckhaltender-11371549.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371549 + Tue, 21 Jul 2026 08:45:00 +0200 + +

Nach dem Shitstorm im März zeigte Nvidia DLSS 5 auf der Siggraph deutlich zurückhaltender. Ob es auf aktueller Hardware läuft, bleibt offen.

]]>
+ +
+ + Suche nach erdähnlichen Exoplaneten: ESA-Weltraumteleskop Plato fit fürs All + https://www.heise.de/news/Suche-nach-erdaehnlichen-Exoplaneten-ESA-Weltraumteleskop-Plato-fit-fuers-All-11371497.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371497 + Tue, 21 Jul 2026 08:37:00 +0200 + +

Kommenden März will die ESA das Weltraumteleskop Plato starten, das erdähnliche Exoplaneten suchen und erforschen soll. Nun wurde ein Meilenstein geschafft.

]]>
+ +
+ + Lockheed Martins Mikrowellen-Drohnenjäger soll 50 Drohnen pro Flug bekämpfen + https://www.heise.de/news/Lockheed-Martins-Mikrowellen-Drohnenjaeger-soll-50-Drohnen-pro-Flug-bekaempfen-11371519.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371519 + Tue, 21 Jul 2026 08:35:00 +0200 + +

Der unbemannte Drohnenabfangjäger Morfius X-Rotor soll Drohnenschwärme kosteneffizient mit Hochleistungs-Mikrowellen bekämpfen. Ein Prototyp ist in Entwicklung.

]]>
+ +
+ + Anthropic: Fable 5 bleibt nur in teuren Abos ohne Aufpreis nutzbar + https://www.heise.de/news/Anthropic-Fable-5-bleibt-nur-in-teuren-Abos-ohne-Aufpreis-nutzbar-11371513.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371513 + Tue, 21 Jul 2026 08:11:00 +0200 + +

Nach drei verschobenen Fristen steht fest: Fable 5 bleibt nur in teuren Claude-Plänen ohne Aufpreis nutzbar. Pro-Abonnenten bekommen einmaliges Guthaben.

]]>
+ +
+ + heise-Angebot: Digitale Souveränität in der Praxis: Strategien, Fallstudien, Know-how + https://www.heise.de/news/Digitale-Souveraenitaet-in-der-Praxis-Strategien-Fallstudien-Know-how-11371032.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371032 + Tue, 21 Jul 2026 08:00:00 +0200 + +

Der IT Summit 2026 zeigt IT-Verantwortlichen konkrete Wege, wie sie ihr Unternehmen bei KI, Arbeitsplatz und Infrastruktur digital souveräner aufstellen können.

]]>
+ +
+ + E-Auto-Förderung geht häufig an Haushalte mit wenig Geld + https://www.heise.de/news/E-Auto-Foerderung-geht-haeufig-an-Haushalte-mit-wenig-Geld-11371477.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371477 + Tue, 21 Jul 2026 07:59:00 +0200 + +

Die E-Auto-Förderung soll Menschen mit wenig Geld einen Kauf ermöglichen, offenbar mit Erfolg. Nun fordern einige, die Unterstützung auf Gebrauchte auszuweiten.

]]>
+ +
+ + Handyverbot in Tschechien: Kritik an populistischem Vorstoß + https://www.heise.de/news/Tschechien-bringt-Handyverbot-an-Schulen-auf-den-Weg-11371489.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371489 + Tue, 21 Jul 2026 07:28:00 +0200 + +

Ab September 2027 sollen Smartphones und Smartwatches aus Tschechiens Klassenzimmern verschwinden. Kritiker halten das für Populismus – und warnen vor Frust.

]]>
+ +
+ + „Höchste Entschädigungssumme“: Anthropic zahlt an Autoren Milliardenstrafe + https://www.heise.de/news/Hoehere-Summe-abgewendet-Anthropic-zahlt-in-Urheberrechtsstreit-Milliardenstrafe-11371473.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371473 + Tue, 21 Jul 2026 07:03:00 +0200 + +

Fürs KI-Training hat Anthropic urheberrechtswidrige Buchsammlungen benutzt. Dafür wird jetzt eine Milliardenstrafe fällig – um eine höhere Summe zu vermeiden.

]]>
+ +
+ + Dienstag: Gericht stoppt Fusion, Cyberkrimineller legt Immobilienmarkt lahm + https://www.heise.de/news/Dienstag-Gericht-stoppt-Fusion-Cyberkrimineller-legt-Immobilienmarkt-lahm-11371461.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371461 + Tue, 21 Jul 2026 06:15:00 +0200 + +

Warner-Übernahme auf Eis + Cyberangriff auf Katasteramt + Telekom bleibt am Ball + Tankrabatt wird nicht erneuert + Leitlinien zur Kennzeichnung von KI-Inhalten

]]>
+ +
+ + Rumänien: Cyberkrimineller löscht die gesamte Grundbuchdatenbank des Landes + https://www.heise.de/news/Rumaenien-Cyberkrimineller-loescht-die-gesamte-Grundbuchdatenbank-des-Landes-11371451.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371451 + Tue, 21 Jul 2026 05:15:00 +0200 + +

Ein Angreifer löscht die gesamte rumänische Grundbuchdatenbank, nachdem eine Erpressung scheiterte, und bringt damit den Immobilienmarkt zum Stillstand.

]]>
+ +
+ + US-Gericht stoppt vorübergehend Warner Bros.-Übernahme durch Paramount + https://www.heise.de/news/US-Gericht-stoppt-voruebergehend-Warner-Bros-Uebernahme-durch-Paramount-11371441.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371441 + Mon, 20 Jul 2026 22:36:00 +0200 + +

Nach einer Klage mehrerer US-Bundesstaaten stoppt eine Richterin vorerst die Paramount-Warner-Fusion und gibt ihnen zwei Wochen Zeit, gegen den Deal vorzugehen.

]]>
+ +
+ + Telekom sichert sich Übertragungsrechte für Fußball-WM 2030 + https://www.heise.de/news/Auch-2030-Telekom-bleibt-bei-der-WM-am-Ball-11371397.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371397 + Mon, 20 Jul 2026 21:15:00 +0200 + +

Die Telekom hat sich auch die Rechte für die nächste Veranstaltung 2030 gesichert. Mehrere Spiele werden trotzdem auch im Free-TV zu sehen sein.

]]>
+ +
+ + Top 10: Die beste Android-Box fürs Auto-Display + https://www.heise.de/bestenlisten/testsieger/top-10-die-beste-android-box-fuers-auto-display-youtube-netflix-und-co-nutzen/cf28g2v?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371286 + Mon, 20 Jul 2026 20:00:00 +0200 + + Streaming im Auto? Eine Android-Box bringt Youtube, Netflix & Co. über den Carplay-Anschluss aufs Infotainment-Display.

]]>
+ +
+ + Vor 50 Jahren: Viking 1 landet auf dem Mars + https://www.heise.de/news/50-Jahre-Marslandung-Viking-1-verfehlte-den-200-Unabhaengigkeitstag-11371365.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371365 + Mon, 20 Jul 2026 19:13:00 +0200 + +

Mehrere sowjetische Versuche zuvor schlugen fehl, so konnte die NASA am 20. Juli 1976 die erste erfolgreiche Marslandung für sich verbuchen.

]]>
+ +
+ + Hunderttausende Opfer: Schlag gegen Phishing-Service + https://www.heise.de/news/Hunderttausende-Opfe-Schlag-gegen-Phishing-Service-11371371.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371371 + Mon, 20 Jul 2026 18:42:00 +0200 + +

Ermittler aus Deutschland und den USA haben die Infrastruktur des Phishing-Services Kratos zerschlagen. Der Dienst ermöglichte monatlich tausende Angriffe.

]]>
+ +
+ + EU-Parlament baut eigene KI-Plattform für Abgeordnete auf + https://www.heise.de/news/EU-Parlament-baut-eigene-KI-Plattform-fuer-Abgeordnete-auf-11371238.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371238 + Mon, 20 Jul 2026 18:34:00 +0200 + +

Rund 2100 Parlamentsmitarbeiter nutzen täglich KI-Tools – mit entsprechenden Qualitätseinbußen. Der EPGenAI Hub soll ab September sicherere Abhilfe schaffen.

]]>
+ +
+ + Microsoft setzt AMDs Helios-Server mit Next-Gen-Hardware ein + https://www.heise.de/news/Microsoft-setzt-AMDs-Helios-Server-mit-Next-Gen-Hardware-ein-11371300.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371300 + Mon, 20 Jul 2026 17:51:00 +0200 + +

AMD zieht einen Deal in der Azure-Cloud an Land. Microsoft kauft Epyc-Venice-Prozessoren und hochpreisige KI-Beschleuniger.

]]>
+ +
+ + EU-Leitlinien für KI-Kennzeichnungspflichten ab August wirksam + https://www.heise.de/news/EU-Leitlinien-fuer-KI-Kennzeichnungspflichten-ab-August-wirksam-11371018.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371018 + Mon, 20 Jul 2026 16:45:00 +0200 + +

Die EU-Kommission hat Leitlinien zur Kennzeichnung von KI-Inhalten verabschiedet. Ab August müssen Anbieter KI-generierte Inhalte transparent ausweisen.

]]>
+ +
+ + Ab 2028: Britische E-Auto-Fahrer zahlen Steuer pro Meile + https://www.heise.de/news/Grossbritannien-E-Autos-werden-streckenabhaengig-steuerpflichtig-11371258.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371258 + Mon, 20 Jul 2026 16:38:00 +0200 + +

Um Fahrer von E-Autos angemessen an den Infrastrukturkosten zu beteiligen, wird in Großbritannien künftig eine Gebühr je Meile fällig.

]]>
+ +
+ + Turmventilator Levoit Windi Mini Pro mit Akku im Test: leise, mobil, stark + https://www.heise.de/bestenlisten/testbericht/turmventilator-levoit-windi-mini-pro-mit-akku-im-test-leise-mobil-stark/g0zktwb?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371200 + Mon, 20 Jul 2026 16:00:00 +0200 + + Der Levoit Windi Mini Pro ist ein kompakter Mini-Turmventilator mit Akku, Ladestation, Oszillation und Stimmungslicht. Wir haben uns ihn ganz genau angeschaut.

]]>
+ +
+ + Valve stimmt auf weiter steigende Speicherpreise ein + https://www.heise.de/news/Valve-stimmt-auf-weiter-steigende-Speicherpreise-ein-11371184.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371184 + Mon, 20 Jul 2026 16:00:00 +0200 + +

PC-Selbstbauern stehen voraussichtlich weitere Preiserhöhungen beim Arbeitsspeicher und bei SSDs ins Haus. Ein Valve-Mitarbeiter warnt.

]]>
+ +
+ + Kimi K3: Moonshot AI stoppt neue Abonnements wegen GPU-Engpass + https://www.heise.de/news/Kimi-K3-Moonshot-AI-stoppt-neue-Abonnements-wegen-GPU-Engpass-11365772.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11365772 + Mon, 20 Jul 2026 15:41:00 +0200 + +

Die Nachfrage nach dem KI-Modell Kimi K3 übersteigt die Rechenkapazitäten von Moonshot AI. Neue Abonnements lassen sich vorerst nicht abschließen.

]]>
+ +
+ + IPFire: Knot Resolver ersetzt Unbound + https://www.heise.de/news/IPFire-Knot-Resolver-ersetzt-Unbound-11371136.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371136 + Mon, 20 Jul 2026 15:00:00 +0200 + +

IPFire Core Update 203 ersetzt Unbound durch Knot Resolver, bringt DNS-Firewall, DoT und 6-GHz-WLAN.

]]>
+ +
+ + Elektroautos: Sinkende Preise, aber wachsende Modell- und Preislücke + https://www.heise.de/hintergrund/Elektroautos-Sinkende-Preise-aber-wachsende-Modell-und-Preisluecke-11370606.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370606 + Mon, 20 Jul 2026 14:55:00 +0200 + +

Vergleichbare E-Auto-Modelle kosten real 18 Prozent weniger als 2020. Doch der Trend zu größeren Fahrzeugen treibt den Medianpreis auf 53.000 Euro.

]]>
+ +
+ + Spritpreise steigen rasant: Kein neuer Tankrabatt geplant + https://www.heise.de/news/Sprit-immer-teurer-Regierung-plant-keine-Entlastungen-11371006.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371006 + Mon, 20 Jul 2026 14:23:00 +0200 + +

Diesel hat sich binnen zwei Wochen um 23 Cent verteuert. Bei E10 ist es nicht mehr weit zu Rekordniveaus. Einen neuen Tankrabatt soll es vorerst nicht geben.

]]>
+ +
+ + Expeditionsschiff Tara Polar Station startet ins Nordmeer + https://www.heise.de/news/Expeditionsschiff-Tara-Polar-Station-soll-ein-Jahr-durch-das-Nordmeer-driften-11371012.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11371012 + Mon, 20 Jul 2026 14:01:00 +0200 + +

Das Expeditionsschiff Tara Polar Station hat seine Reise ins Nordmeer angetreten. Es soll ein Jahr lang durch die Arktis driften und Daten sowie Proben sammeln.

]]>
+ +
+ + heise-Angebot: iX-Workshop: ISO 27001 als Admin im Unternehmen praktisch umsetzen + https://www.heise.de/news/iX-Workshop-ISO-27001-als-Admin-im-Unternehmen-praktisch-umsetzen-11362144.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11362144 + Mon, 20 Jul 2026 14:00:00 +0200 + +

Administratoren und IT-Verantwortliche lernen ihre Rolle im ISMS kennen und trainieren, wie sie Maßnahmen der ISO 27001 technisch und organisatorisch umsetzen.

]]>
+ +
+ + Stromausfall Berlin: Beschuldigte wehren sich gegen Auswertung von Mobilgeräten + https://www.heise.de/news/Stromausfall-Berlin-Beschuldigte-wehren-sich-gegen-Auswertung-von-Mobilgeraeten-11370842.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370842 + Mon, 20 Jul 2026 13:58:00 +0200 + +

Nach einem Anschlag auf die Berliner Stromversorgung wehren sich vier Tatverdächtige gegen die Beschlagnahmung von Smartphones und anderen Geräten.

]]>
+ +
+ + Neue Version von ChatGPT löscht selbständig Dateien + https://www.heise.de/news/Neue-Version-von-ChatGPT-loescht-selbstaendig-Dateien-11370924.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370924 + Mon, 20 Jul 2026 13:31:00 +0200 + +

Laut OpenAI handelt es sich bei dem Vorgang um ein selten vorkommendes Versehen. Allerdings wurde das Verhalten des LLMs im Entwicklungsprozess vorhergesagt.

]]>
+ +
+ + „HollowByte“: Denial-of-Service-Lücke in OpenSSL + https://www.heise.de/news/HollowByte-Denial-of-Service-Luecke-in-OpenSSL-11370866.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370866 + Mon, 20 Jul 2026 13:17:00 +0200 + +

Die OpenSSL-Maintainer haben stillschweigend eine Denial-of-Service-Lücke geschlossen. Okta nennt sie „HollowByte“.

]]>
+ +
+ + AliExpress: EU-Kommission verhängt DSA-Rekordstrafe + https://www.heise.de/news/AliExpress-EU-Kommission-verhaengt-DSA-Rekordstrafe-11370852.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370852 + Mon, 20 Jul 2026 12:45:00 +0200 + +

Der chinesische Onlinemarktplatz AliExpress hat über Jahre gegen den Digital Services Act verstoßen und muss nun 550 Millionen Euro zahlen.

]]>
+ +
+ + iPad mini bald mit OLED – aber wohl ohne ProMotion + https://www.heise.de/news/iPad-mini-bald-mit-OLED-aber-wohl-ohne-ProMotion-11369648.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11369648 + Mon, 20 Jul 2026 12:45:00 +0200 + +

Apple steht kurz vor der Überholung seines Kompakt-Tablets – und bereitet auch neue Modelle weiterer Baureihen vor. Doch gibt es technische Defizite?

]]>
+ +
+ + Angriff aufs Grundgesetz? Dobrindt will Inlandsgeheimdienst Razzien erlauben + https://www.heise.de/news/Angriff-aufs-Grundgesetz-Dobrindt-will-Inlandsgeheimdienst-Razzien-erlauben-11370690.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370690 + Mon, 20 Jul 2026 12:38:00 +0200 + +

Innenminister Dobrindt treibt eine radikale Reform des Nachrichtendienstrechts voran. Agenten des Verfassungsschutzes sollen sogar Wohnungen durchsuchen dürfen.

]]>
+ +
+ + Typ des finalen Asteroiden identifiziert: Dinosaurier mit besonders „viel Pech“ + https://www.heise.de/news/Asteroideneinschlag-Dinosaurier-von-aeusserst-seltenem-Meteoritentyp-ausgeloescht-11370610.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370610 + Mon, 20 Jul 2026 12:05:00 +0200 + +

Der Asteroid, der vor 66 Millionen Jahren die Dinosaurier ausgelöscht hat, gehörte wohl einer seltenen Klasse an. Das Aussterben lief anders ab als gedacht.

]]>
+ +
+ + heise-Angebot: Machine Learning mit Python – KI und Deep Learning in 5 Sessions erklärt + https://www.heise.de/news/Machine-Learning-mit-Python-KI-und-Deep-Learning-in-5-Sessions-erklaert-11338933.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11338933 + Mon, 20 Jul 2026 12:00:00 +0200 + +

Ab dem 26.08. lernen Sie in fünf Sessions künstliche Intelligenz zu entwickeln: Machine Learning, neuronale Netze und Deep Learning – alles effizient in Python.

]]>
+ +
+ + Microsoft arbeitet an Lösung für WSUS-Störungen + https://www.heise.de/news/Microsoft-raeumt-Stoerungen-der-WSUS-Dienste-ein-11370682.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370682 + Mon, 20 Jul 2026 11:56:00 +0200 + +

Die Update-Software WSUS hat seit vergangener Woche mit Problemen zu kämpfen, hat Microsoft nun eingeräumt. Es gibt erste Fixes.

]]>
+ +
+ + Mac Pro: Apple dachte an neues Modell – und gleich zwei „Extreme“-Chips + https://www.heise.de/news/Mac-Pro-Apple-dachte-an-neues-Modell-und-gleich-zwei-Extreme-Chips-11370150.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370150 + Mon, 20 Jul 2026 11:47:00 +0200 + +

Apples schnellster Rechner ist mittlerweile der Mac Studio. Das war lange anders. Nun gibt es neue Details zu alten Planungen im Hinblick auf High-End-Systeme.

]]>
+ +
+ + Gotify 3.0.0: Push-Nachrichten selbst hosten + https://www.heise.de/news/Gotify-3-0-0-Push-Nachrichten-selbst-hosten-11370630.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370630 + Mon, 20 Jul 2026 11:41:00 +0200 + +

Gotify 3.0.0 führt OpenID Connect für die Anmeldung ein, verschärft den Umgang mit API-Token und überarbeitet die Konfiguration.

]]>
+ +
+ + Bethesda kündigt „Fallout 5“ und Remaster von „Fallout 3“ an + https://www.heise.de/news/Vier-neue-Fallout-Spiele-Fallout-5-und-mehrere-Remaster-in-Arbeit-11370598.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370598 + Mon, 20 Jul 2026 11:34:00 +0200 + +

Bethesda arbeitet an „Fallout 5“, Remastern von „Fallout 3“ und „New Vegas“ sowie einem neuen „Fallout“ von Obsidian. Termine gibt es bisher keine.

]]>
+ +
+ + Apple Music wird in Deutschland teurer + https://www.heise.de/news/Apple-Music-wird-in-Deutschland-teurer-11370158.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370158 + Mon, 20 Jul 2026 10:52:00 +0200 + +

Apple macht seinen populären Musikstreamingdienst teurer, Deutschland inklusive. iCloud+ ist in anderen Regionen betroffen.

]]>
+ +
+ + Volkswagen sperrt Nutzer von Custom ROMs aus der VW-App aus + https://www.heise.de/news/Volkswagen-sperrt-Nutzer-von-Custom-ROMs-aus-der-VW-App-aus-11370478.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370478 + Mon, 20 Jul 2026 10:21:00 +0200 + +

Volkswagen hat den Zugriff auf seine App für Nutzer von Custom ROMs wie GrapheneOS oder LineageOS gesperrt. Grund ist die Google Play Integrity API.

]]>
+ +
+ + Hugging Face: KI-Plattform wurde Opfer eines KI-Angriffs + https://www.heise.de/news/Hugging-Face-IT-Sicherheitsvorfall-bei-KI-Plattform-11370448.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370448 + Mon, 20 Jul 2026 10:18:00 +0200 + +

Die Plattform zum Teilen quelloffener KI-Modelle und -Datensätze Hugging Face wurde Opfer eines Cyberangriffs einer KI.

]]>
+ +
+ + iPhone-Preise beginnen zu steigen – aber vorerst nur in einem Land + https://www.heise.de/news/iPhone-Preise-beginnen-zu-steigen-aber-vorerst-nur-in-einem-Land-11370154.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370154 + Mon, 20 Jul 2026 10:18:00 +0200 + +

Weltweit hat Apple die Tarife für iPads, Macs und Zubehör angezogen, Smartphones blieben verschont. In einem Markt hat sich das nun verändert.

]]>
+ +
+ + heise-Angebot: iX-Workshop: Incident Leadership – Kommunizieren und Entscheiden im Krisenfall + https://www.heise.de/news/iX-Workshop-Incident-Leadership-Kommunizieren-und-Entscheiden-im-Krisenfall-11362140.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11362140 + Mon, 20 Jul 2026 10:00:00 +0200 + +

Lernen Sie, wie Sie unter Druck klare Entscheidungen treffen und ruhig kommunizieren, um Ihr IT-Team sicher durch Krisensituationen zu steuern.

]]>
+ +
+ + Auswirkungen der chinesischen Luxussteuer auf deutsche Premium-Hersteller + https://www.heise.de/news/Auswirkungen-der-chinesischen-Luxussteuer-auf-deutsche-Premium-Hersteller-11370442.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370442 + Mon, 20 Jul 2026 09:58:00 +0200 + +

In China läuft es für die sogenannten Premium-Autohersteller aus Deutschland schon länger nicht mehr rund. Vor einem Jahr kam noch eine Luxusauto-Steuer hinzu.

]]>
+ +
+ + Riesenrakete Starship: Nächster Startversuch erst in der Nacht zu Freitag + https://www.heise.de/news/Riesenrakete-Starship-Naechster-Startversuch-erst-in-der-Nacht-zu-Freitag-11370292.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370292 + Mon, 20 Jul 2026 09:48:00 +0200 + +

Die Behebung der Probleme am Starship dauert ein paar Tage länger als ursprünglich gedacht. Jetzt soll die Riesenrakete in der Nacht zu Freitag abheben.

]]>
+ +
+ + Kritische Sicherheitslücke: Schadcode kann auf Nginx-Server schlüpfen + https://www.heise.de/news/Kritische-Sicherheitsluecke-Schadcode-kann-auf-Nginx-Server-schluepfen-11370300.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370300 + Mon, 20 Jul 2026 09:03:00 +0200 + +

Angreifer können Nginx Open Source und Nginx Plus attackieren. Sicherheitsupdates sind verfügbar.

]]>
+ +
+ + Frankreich zieht bei Polymarket zwei Jahre nach Verwarnung den Stecker + https://www.heise.de/news/Frankreich-sperrt-Zugang-zu-Wettplattform-Polymarket-11370278.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370278 + Mon, 20 Jul 2026 08:55:00 +0200 + +

Frankreichs Glücksspielaufsicht ANJ lässt Internetanbieter den Zugang zu Polymarket sperren – zwei Jahre nach der ersten Mahnung.

]]>
+ +
+ + Shark-Saugroboter: Sicherheitslücke ermöglicht Übernahme aus dem Netz + https://www.heise.de/news/Shark-Saugroboter-Sicherheitsluecke-ermoeglicht-Uebernahme-aus-dem-Netz-11370282.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370282 + Mon, 20 Jul 2026 08:39:00 +0200 + +

Ein IT-Sicherheitsforscher hat eine Schwachstelle in Shark-Saugrobotern entdeckt, die die Übernahme aus dem Internet ermöglicht.

]]>
+ +
+ + heise-Angebot: secIT digital: Grundschutz++/Cyber Resilience Act richtig und effektiv umsetzen + https://www.heise.de/news/secIT-digital-Grundschutz-Cyber-Resilience-Act-richtig-und-effektiv-umsetzen-11351647.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11351647 + Mon, 20 Jul 2026 08:00:00 +0200 + +

Alle Infos auf der secIT digital: Ab September 2026 gelten die ersten Vorgaben des CRA. Das IT-Sicherheitskonzept Grundschutz++ soll 2026 finalisiert werden.

]]>
+ +
+ + Warum in Deutschland so viele Online-Bewertungen verschwinden + https://www.heise.de/news/Viele-Restaurants-und-Hotels-loeschen-Online-Bewertungen-11370258.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370258 + Mon, 20 Jul 2026 07:56:00 +0200 + +

Immer mehr Restaurants und Hotels löschen Bewertungen auf Google. Warum Deutschland dabei europaweit hervorsticht und was Kritik von Fake unterscheidet.

]]>
+ +
+ + Weltraumschrott von SpaceX vor Einschlag auf dem Mond: NASA-Sonde soll nachsehen + https://www.heise.de/news/Weltraumschrott-von-SpaceX-vor-Einschlag-auf-dem-Mond-NASA-Sonde-soll-nachsehen-11370236.html?wt_mc=rss.red.ho.ho.rdf.beitrag.beitrag + + + http://heise.de/-11370236 + Mon, 20 Jul 2026 07:39:00 +0200 + +

In zwei Wochen wird eine SpaceX-Raketenstufe auf dem Mond einschlagen, auf der Erde wird das wohl nicht sichtbar sein. Ein NASA-Orbiter soll später nachsehen.

]]>
+ +
+ + heise+ | Elektroauto Kia EV2 im Test: Kurz und gut + https://www.heise.de/tests/Elektroauto-Kia-EV2-im-Test-Kurz-und-gut-11354828.html?wt_mc=rss.red.ho.ho.rdf.beitrag_plus.beitrag_plus + + + http://heise.de/-11354828 + Mon, 20 Jul 2026 07:00:00 +0200 + +

Gemessen daran, dass der EV2 ein Kleinwagen ist, fährt er ziemlich erwachsen und ist auch mit der kleinen Batterie empfehlenswert, sofern das Profil stimmt.

]]>
+ +
+
+
diff --git a/tests/corpus/rss/heise/expect.json b/tests/corpus/rss/heise/expect.json new file mode 100644 index 0000000..076c5b3 --- /dev/null +++ b/tests/corpus/rss/heise/expect.json @@ -0,0 +1,7 @@ +{ + "type": "rss", + "version": "2.0", + "title": "heise online News", + "items": 153, + "first_item_title": "O2 Telefónica streicht ein Sechstel aller Arbeitsplätze" +} diff --git a/tests/corpus/rss/hnrss/data.xml b/tests/corpus/rss/hnrss/data.xml new file mode 100644 index 0000000..5b9d9ea --- /dev/null +++ b/tests/corpus/rss/hnrss/data.xml @@ -0,0 +1,111 @@ +Hacker News: Front Pagehttps://news.ycombinator.com/Hacker News RSShttps://hnrss.org/hnrss v2.1.1Wed, 22 Jul 2026 23:02:42 +0000<![CDATA[Show HN: ValuePair – a friendship app that cares about values first]]>Hey,

I would like to show you my project, but it's difficult because it only works with a registration, so I explain the concept to you.

The Idea: +Everyone who registers has to do an onboarding and answer meaningful questions that help you to find a right match. The matching happens by the system. Once you're done with the onboarding, you enter the pool. When a match is found, you go into a 1on1 14 question-set with that person. All answers are revealed immediately. At the end both decide if they form a connection or not. Only if both agree, the chat opens. Once a connection is formed, they can play through other prepared question sets, so the conversation doesn't get dry. It's also possible to create custom question sets to play through with your connected matches.

I just got this idea because I really wanted away from all the typical friendship and dating apps that are just a marketplace of user profiles. I feel like that's the only hook other apps offer, people come back because they are bored and browse through profiles. On tinder it's swiping through pictures and on other apps it's a list of profiles where you can filter with your matching criteria. But all that felt just shallow to me. I think people rarely ask the right questions each other, like really mandatory things that matter to form a any kind of relationship with someone. I've been on multiple different social platforms and it always felt mega shallow. Most people can't even start a good conversation, it's often just stuck at small talk. Maybe it's not even peoples fault, that's why I tried with these question sets to tackle multiple problems. First, that you don't even have to bother with someone if you have entirely different worldviews, which you would normally just find out if you talk long enough with someone. And second, just killing the small talk issue, by giving people a base.

So before I started coding this project, I actually did a research if there is anything backed by science to find out, which kind of questions would actually matter to find out, if you can handle someone. There were a lot of studies I've found e.g. "Aron and colleagues (1997)", "Hall (2019)", "Byrne (1971)", "Finkel and colleagues (2012)" and "Hazan and Shaver (1987)". I took their research work and tried to create questions based on them.

The project is running on Next, Postgresql, everything dockerized, running on a small Hetzner VPS. +I used Claude+Codex to develop everything, but I wouldn't say it's really vibecoded, since I work as a Senior Dev and I used my main tech stack.

It's an open beta currently and it's live for like 14 days - I have ~150 users right now. And honestly, I am really overwhelmed by those numbers, because it's the first time that I ever managed to attract this amount of people on a project I've launched.

When I started, I launched it as a closed beta, so it was invite only. During that period I got so much good feedback from people, it was incredible. I implemented almost everything my first users told me. Since all the changes, I didn't retrieve any further feedback what people wish would be different. It's just constant good feedback since then. People contact me from alone and just tell me how much they like these questions and the loop.

My marketing strategy is mainly Reddit currently, but I started also new methods. I found some super relevant subreddits that had no rules against advertising. When I wrote my posts on reddit, I didn't mention a product name or link. I've only described the idea and if somebody would have interest trying it out. That worked surprisingly well. +Another thing I've done was using a social app that allows anonymous posts at your current location, kinda something like "Nextdoor". +Posts on this app bring me daily 1-5 new registrations.

I wonder what you guys think about it and I am open for any critics!

+
+

Comments URL: https://news.ycombinator.com/item?id=49014246

+

Points: 3

+

# Comments: 2

+]]>
Wed, 22 Jul 2026 22:20:00 +0000https://valuepair.appzloy88https://news.ycombinator.com/item?id=49014246https://news.ycombinator.com/item?id=49014246
<![CDATA[Fretboard Memorisation with Modular Arithmetic]]>Article URL: https://ohaodha.ie/blog/fretboard-memorisation-with-modular-arithmetic/

+

Comments URL: https://news.ycombinator.com/item?id=49014143

+

Points: 4

+

# Comments: 0

+]]>
Wed, 22 Jul 2026 22:09:41 +0000https://ohaodha.ie/blog/fretboard-memorisation-with-modular-arithmetic/ohaodhahttps://news.ycombinator.com/item?id=49014143https://news.ycombinator.com/item?id=49014143
<![CDATA[Medici family mystery may be solved after more than 400 years]]>Article URL: https://www.cnn.com/2026/07/15/science/medici-family-mystery-dna-malaria

+

Comments URL: https://news.ycombinator.com/item?id=49014007

+

Points: 35

+

# Comments: 4

+]]>
Wed, 22 Jul 2026 21:55:34 +0000https://www.cnn.com/2026/07/15/science/medici-family-mystery-dna-malariaeffectshttps://news.ycombinator.com/item?id=49014007https://news.ycombinator.com/item?id=49014007
<![CDATA[Any text-to-SQL benchmark should address difficulties of real-world data stores]]>Article URL: https://cacm.acm.org/blogcacm/if-you-think-you-can-do-real-world-text-to-sql/

+

Comments URL: https://news.ycombinator.com/item?id=49013995

+

Points: 18

+

# Comments: 3

+]]>
Wed, 22 Jul 2026 21:54:30 +0000https://cacm.acm.org/blogcacm/if-you-think-you-can-do-real-world-text-to-sql/shenli3514https://news.ycombinator.com/item?id=49013995https://news.ycombinator.com/item?id=49013995
<![CDATA[Malleable Computing, Emacs, and You]]>Article URL: http://yummymelon.com/devnull/malleable-computing-emacs-and-you.html

+

Comments URL: https://news.ycombinator.com/item?id=49013538

+

Points: 42

+

# Comments: 5

+]]>
Wed, 22 Jul 2026 21:15:26 +0000http://yummymelon.com/devnull/malleable-computing-emacs-and-you.htmlkickingvegashttps://news.ycombinator.com/item?id=49013538https://news.ycombinator.com/item?id=49013538
<![CDATA[Safari Technology Preview 248 Released]]>Article URL: https://webkit.org/blog/18162/release-notes-for-safari-technology-preview-248/

+

Comments URL: https://news.ycombinator.com/item?id=49013356

+

Points: 51

+

# Comments: 11

+]]>
Wed, 22 Jul 2026 21:00:26 +0000https://webkit.org/blog/18162/release-notes-for-safari-technology-preview-248/Erenay09https://news.ycombinator.com/item?id=49013356https://news.ycombinator.com/item?id=49013356
<![CDATA[I Inspected My Take-Home Interview Project. It Was a Whole Operation]]>Article URL: https://citizendot.github.io/articles/fake-job-interview-git-hook-malware/

+

Comments URL: https://news.ycombinator.com/item?id=49013036

+

Points: 199

+

# Comments: 43

+]]>
Wed, 22 Jul 2026 20:33:44 +0000https://citizendot.github.io/articles/fake-job-interview-git-hook-malware/CITIZENDOThttps://news.ycombinator.com/item?id=49013036https://news.ycombinator.com/item?id=49013036
<![CDATA[Fairphone 6 wide camera experimental Linux support]]>Article URL: https://nondescriptpointer.com/articles/fairphone-6-wide-camera-linux/

+

Comments URL: https://news.ycombinator.com/item?id=49012777

+

Points: 31

+

# Comments: 0

+]]>
Wed, 22 Jul 2026 20:16:01 +0000https://nondescriptpointer.com/articles/fairphone-6-wide-camera-linux/helonauthttps://news.ycombinator.com/item?id=49012777https://news.ycombinator.com/item?id=49012777
<![CDATA[John C. Dvorak has died]]>https://xcancel.com/na_announce/status/2079952538040672302

+
+

Comments URL: https://news.ycombinator.com/item?id=49012070

+

Points: 406

+

# Comments: 115

+]]>
Wed, 22 Jul 2026 19:22:19 +0000https://twitter.com/na_announce/status/2079952538040672302colecahttps://news.ycombinator.com/item?id=49012070https://news.ycombinator.com/item?id=49012070
<![CDATA[Show HN: Cactus Hybrid: We taught Gemma 4 to know when it's wrong]]>Hey HN, Henry & Roman here from Cactus.

A small, on-device model is fast and private, but sometimes wrong, but frontier models are getting expensive pretty fast. So, we post-trained Gemma 4 E2B post-trained to know when it's wrong. Every response comes with a confidence score between 0 and 1. Developers can accept the on-device when it's high, hand off to a bigger cloud model when it's low. By routing only 15-35% of queries to Gemini 3.1 Flash-Lite, Gemma-4-E2B matches Gemini 3.1 Flash-Lite on most benchmarks.

- ChartQA: 15-20%

- LibriSpeech: 25-30%

- MMBench, GigaSpeech, MMAU: 30-35%

- MMLU-Pro: 45-55%

We were always frustrated by the routing signals hybrid apps rely on: asking the model to rate itself in text (unreliable, and you're parsing prose), or token entropy heuristics (barely better than a coin flip in our tests). So we did mechanistic studies on small models, Gemma 4 particularly, and found the hidden state for different layers carry meaningful self-awareness signal for various situations.

SO we extended the model with a 68k params probe layer (LayerNorm, low-rank projection, attention pooling, small MLP head) reads one intermediate layer during decoding and predicts p(wrong); confidence = 1 - p(wrong), returned as structured data, never parsed out of the answer text.

Across 12 hold-out benchmarks spanning text, vision and audio, the probe averages 0.814 AUROC vs 0.549 for token entropy. The result that convinced us this is real: the probe was trained on zero audio data, yet scores 0.79-0.88 AUROC on four audio benchmarks where entropy is near-random or worse (0.32-0.52). It's reading a modality-independent correctness signal from the hidden state, not memorizing patterns from its training data.

We published all weights on HuggingFace and provide copy-pase codes to run it on Transformers, MLX, Llama.cpp or Cactus. With Ollama, vLLM, SGLang etc in the works. For llama.cpp we ship a patch series you compile in once (upstreaming is planned). The code is MIT licensed; Gemma model use remains subject to the Gemma terms.

GitHub: https://github.com/cactus-compute/cactus-hybrid

Weights: https://huggingface.co/collections/Cactus-Compute/cactus-hyb...

Some caveats:

- The probe scores single-sequence decoding only, up to the first 1024 generated tokens.

- Handoff works best when routing per task in a multi-step process, not per step.

- Hierarchical routing is still in the works: try on-device, then DeepSeek v4 Flash, before Fable/GPT5.5/Gemini/Muse/Grok.

- The technique is boutique for each model, we will share each weights as they roll out.

These issues are currently being tackled at Cactus and updated weights will be shipped directly into the HuggingFace collection and GitHub repository straight up. Please let us know your thoughts, it helps us find ways to improve the design progressively.

Thanks a million!

+
+

Comments URL: https://news.ycombinator.com/item?id=49010782

+

Points: 18

+

# Comments: 3

+]]>
Wed, 22 Jul 2026 17:56:29 +0000https://github.com/cactus-compute/cactus-hybridHenryNdubuakuhttps://news.ycombinator.com/item?id=49010782https://news.ycombinator.com/item?id=49010782
<![CDATA[Everyone Should Know SIMD]]>Article URL: https://mitchellh.com/writing/everyone-should-know-simd

+

Comments URL: https://news.ycombinator.com/item?id=49010648

+

Points: 183

+

# Comments: 55

+]]>
Wed, 22 Jul 2026 17:48:18 +0000https://mitchellh.com/writing/everyone-should-know-simdWadeGrimridgehttps://news.ycombinator.com/item?id=49010648https://news.ycombinator.com/item?id=49010648
<![CDATA[Terrence Tao's ChatGPT Conversation about the Jacobian Conjecture Counterexample]]>Article URL: https://chatgpt.com/share/6a5fdc7a-d6f8-83e8-bbea-8deb42cfed56

+

Comments URL: https://news.ycombinator.com/item?id=49010345

+

Points: 496

+

# Comments: 299

+]]>
Wed, 22 Jul 2026 17:30:40 +0000https://chatgpt.com/share/6a5fdc7a-d6f8-83e8-bbea-8deb42cfed56gmayshttps://news.ycombinator.com/item?id=49010345https://news.ycombinator.com/item?id=49010345
<![CDATA[GigaToken: ~1000x faster Language model tokenization]]>Article URL: https://github.com/marcelroed/gigatoken/

+

Comments URL: https://news.ycombinator.com/item?id=49010167

+

Points: 307

+

# Comments: 57

+]]>
Wed, 22 Jul 2026 17:20:38 +0000https://github.com/marcelroed/gigatoken/syrusakbaryhttps://news.ycombinator.com/item?id=49010167https://news.ycombinator.com/item?id=49010167
<![CDATA[Are AI Labs Pelicanmaxxing?]]>Article URL: https://dylancastillo.co/posts/pelicanmaxxing.html

+

Comments URL: https://news.ycombinator.com/item?id=49010129

+

Points: 328

+

# Comments: 131

+]]>
Wed, 22 Jul 2026 17:17:54 +0000https://dylancastillo.co/posts/pelicanmaxxing.htmldcastmhttps://news.ycombinator.com/item?id=49010129https://news.ycombinator.com/item?id=49010129
<![CDATA[Launch HN: Unlayer (YC W22) – Add email and document builders to your app]]>Hi HN, We’re Adeel and Umair, co-founders of Unlayer (https://unlayer.com/). We let you add content creation to your applications without having to build an entire editor, renderer, template, and export stack yourself. Unlayer lets you create emails, web pages, and documents inside your app, in three different ways: in code, visually, or with AI.

Here’s a demo: https://www.youtube.com/watch?v=0HsDtNkdMpM.

We started with an embeddable email editor because a lot of products eventually need one: CRMs, marketing tools, customer engagement platforms, marketplaces, internal tools, and vertical SaaS apps all run into this at some point. At first, it sounds like a small feature: "just" add a drag and drop editor. In practice, it turns into a big pain. You end up dealing with email rendering, Outlook quirks, responsive layouts, templates, merge tags, image uploads, exports, permissions, localization, versioning, and a long tail of edge cases that have nothing to do with your core product

Over time, we saw the same problem beyond email. Apps also need landing pages, invoices, proposals, reports, contracts, and PDFs. Some of this content is best created visually by end users. Some of it is better generated in code by developers. Increasingly, some of it is also generated by AI agents. Many teams eventually need all three workflows. That is the direction we have been working toward with Unlayer.

There are three parts we are showing today:

(1) Unlayer Elements. This is our open-source React component library for creating emails, pages, and documents in code (repo: https://github.com/unlayer/elements, more at https://unlayer.com/elements). Instead of hand-writing raw HTML templates, developers can compose content using React components, reuse sections like headers, footers, CTAs, invoice rows, and branded blocks, keep templates in Git, and render them into production output.

One newer use case we are seeing is AI-assisted content creation. If an AI agent is asked to create an email, invoice, report, or landing page, the output is usually raw HTML or markdown that becomes hard to maintain. With Elements, the agent can generate structured React components instead. A developer can review the result, refactor it, keep it in Git, and still pass the design into a visual builder later if someone needs to edit it.

(2) Visual Builder. This is the drag and drop editor (repo: https://github.com/unlayer/react-email-editor, more at https://unlayer.com/email-builder) that can be embedded inside an app so non-technical users can create or edit content. In the demo, we show the email builder and the AI assistant inside the builder. The goal is not to replace the developer workflow, but to connect it with a visual workflow when marketers, admins, customers, or internal teams need to make changes themselves.

(3) Document Builder. This is for structured documents such as proposals, reports, invoices, contracts, and PDFs. We have seen a lot of teams build separate systems for email templates, web pages, and document generation, even though the underlying primitives are similar: layout, content blocks, variables, assets, preview, export, and permissions. More: https://unlayer.com/document-builder

The technical challenge is making these workflows share a common foundation. Developers should be able to build templates in code when that makes sense. End users should be able to edit visually when that makes sense. AI agents should be able to generate structured content instead of unmaintainable blobs. The final output should still be usable by the host application.

We make money by selling hosted builder, template, export, and platform features to companies embedding this into their products. Elements is open source. The commercial product is the broader hosted platform around builders, collaboration, storage, exports, and production use cases.

We were part of W22, so this is a late Launch HN. At the time, Unlayer was an embeddable email editor, and we did not think we had the right broader story for HN yet. Since then, the product has expanded into a more general content creation layer for products, including emails, pages, documents, APIs, open-source developer projects, and AI-assisted workflows. That felt like a better moment to bring it to HN and ask for feedback.

We'd really appreciate thoughts from HN. This is one of those areas where a lot of people have strong opinions because they’ve been burned by editors, email HTML, document editing, or “simple” content workflows before. We'd love to hear what resonates, what sounds wrong, and what you think we should be thinking harder about!

+
+

Comments URL: https://news.ycombinator.com/item?id=49008901

+

Points: 43

+

# Comments: 22

+]]>
Wed, 22 Jul 2026 16:02:03 +0000https://unlayer.comadeelrazahttps://news.ycombinator.com/item?id=49008901https://news.ycombinator.com/item?id=49008901
<![CDATA[Can a MUD evaluate LLMs? A $99 proof of concept]]>I'm the author of a paper my friends and I wrote after we were curious if a MUD, text games originating in the 1970s, could be used to evaluate LLMs. We've spent the last several months on nights and weekends running this experiment and writing the paper on just our personal computers with about $99 in API credits.

Our experiment did have an interesting leaderboard but even more surprising was the measurements of each LLM. We scored each on four behavioral dimensions, two of which lean heavily on an LLM classifier. When we removed those two, one of the frontier models fell six positions. When we then checked the classifier against a second judge, the per-model agreement between them ranged from 85% to 22%. The aggregate kappa (0.04 on probe detection) indicated the instrument was noisy without saying which models the noise was hitting. The most affected model shared a model family with the classifier. This isn't proof of bias, just one observation we recorded.

We realize LLM judges can be unreliable, and while it wasn't our original intent to test this, it ended up being the most interesting finding. The divergence between the two judges is the finding we think generalizes to other judge-based benchmarks.

We emphasize this is just a proof of concept and not a validated benchmark. We prepared a thorough limitations section in the paper, including just 50 runs per model, overlapping CIs among the top models, no human raters, a tiny environment, etc.

Everything we did is publicly available, the paper and data are CC BY 4.0, while the code is MIT. The paper, transcripts, code, and complete API billing export can be found at https://doi.org/10.5281/zenodo.21386663

If you find issues, please let us know, that's why we're sharing this. We're currently designing Phase 2 and want it to be as robust as possible. We're looking at human baselines, multiple judges, more objectives, a larger environment, etc.

+
+

Comments URL: https://news.ycombinator.com/item?id=49008538

+

Points: 91

+

# Comments: 57

+]]>
Wed, 22 Jul 2026 15:39:01 +0000https://cruciblebench.ai/Davisb135https://news.ycombinator.com/item?id=49008538https://news.ycombinator.com/item?id=49008538
<![CDATA[Making]]>Article URL: https://beej.us/blog/data/ai-making/

+

Comments URL: https://news.ycombinator.com/item?id=49008440

+

Points: 247

+

# Comments: 103

+]]>
Wed, 22 Jul 2026 15:33:48 +0000https://beej.us/blog/data/ai-making/erikschosterhttps://news.ycombinator.com/item?id=49008440https://news.ycombinator.com/item?id=49008440
<![CDATA[Show HN: Bento - An entire PowerPoint in one HTML file (edit+view+data+collab)]]>Over the past few months, our team has been building more and more slidedecks using web frontend technologies with coding harnesses like Claude Code, but a common complaint is to make even small edits we need to edit the code either manually or via the harness.

To avoid this loop, I ended up creating Bento, a single HTML file with everything you need in a slide tool including animations and shared editing. There's no install or cloud login, everything works offline. The default deck is around 560 KB and it doesn't need to fetch anything once you got it.

Open it in a browser and then you can edit, present, print and save. Share it via email or via Airdrop and all they need is a browser to edit, present and also do live collab on the slides. Drop it in to Claude or ChatGPT to transform existing pptx files into Bento slides. There is no cloud involved, only an encrypted blind relay to allow for shared editing. The relay doesn't see any of the data.

Check it out at https://bento.page/slides/ which takes you straight to the editor.

Go to https://bento.page/guestbook/ to try out the live guestbook to experience share editing / collab.

There is also a gallery with some sample decks on the website - https://bento.page/

All the code is MIT licensed and you can find it here - https://github.com/nyblnet/bento . I used reveal.js with several other libraries (including some homegrown ones), and Claude Code.

+
+

Comments URL: https://news.ycombinator.com/item?id=49008211

+

Points: 578

+

# Comments: 140

+]]>
Wed, 22 Jul 2026 15:19:23 +0000https://bento.page/slides/starfallghttps://news.ycombinator.com/item?id=49008211https://news.ycombinator.com/item?id=49008211
<![CDATA[Which streaming service was that on again?]]>Article URL: https://www.timwehrle.de/blog/which-streaming-service-was-that-on-again/

+

Comments URL: https://news.ycombinator.com/item?id=49007671

+

Points: 39

+

# Comments: 59

+]]>
Wed, 22 Jul 2026 14:46:14 +0000https://www.timwehrle.de/blog/which-streaming-service-was-that-on-again/weetiihttps://news.ycombinator.com/item?id=49007671https://news.ycombinator.com/item?id=49007671
<![CDATA[Ghost Cut – or why Cut and Paste is broken everywhere]]>Article URL: https://ishmael.textualize.io/blog/ghost-cut/

+

Comments URL: https://news.ycombinator.com/item?id=49007626

+

Points: 112

+

# Comments: 80

+]]>
Wed, 22 Jul 2026 14:43:28 +0000https://ishmael.textualize.io/blog/ghost-cut/willmhttps://news.ycombinator.com/item?id=49007626https://news.ycombinator.com/item?id=49007626
\ No newline at end of file diff --git a/tests/corpus/rss/hnrss/expect.json b/tests/corpus/rss/hnrss/expect.json new file mode 100644 index 0000000..2e0f605 --- /dev/null +++ b/tests/corpus/rss/hnrss/expect.json @@ -0,0 +1,7 @@ +{ + "type": "rss", + "version": "2.0", + "title": "Hacker News: Front Page", + "items": 20, + "first_item_title": "Show HN: ValuePair – a friendship app that cares about values first" +} diff --git a/tests/corpus/rss/npr/data.xml b/tests/corpus/rss/npr/data.xml new file mode 100644 index 0000000..e2b4bd8 --- /dev/null +++ b/tests/corpus/rss/npr/data.xml @@ -0,0 +1,107 @@ + + + + NPR Topics: News + https://www.npr.org/templates/story/story.php?storyId=1001 + NPR news, audio, and podcasts. Coverage of breaking stories, national and world news, politics, business, science, technology, and extended coverage of major national and world events. + en + Copyright 2024 NPR - For Personal Use Only + Story API Shim 1.2.24 + Wed, 22 Jul 2026 19:07:09 -0400 + + https://media.npr.org/images/podcasts/primary/npr_generic_image_300.jpg?s=200 + NPR Topics: News + https://www.npr.org/sections/news/ + + + House passes Pentagon funding and limits on stock trades in a last dash before recess + Republicans passed more than $1 trillion for the Pentagon alongside a budget blueprint to fund the war with Iran and implement provisions of President Trump's election overhaul bill. + Wed, 22 Jul 2026 17:51:19 -0400 + https://www.npr.org/2026/07/22/nx-s1-5903130/house-vote-iran-war-funding-reconciliation + https://www.npr.org/2026/07/22/nx-s1-5903130/house-vote-iran-war-funding-reconciliation +

Republicans passed more than $1 trillion for the Pentagon alongside a budget blueprint to fund the war with Iran and implement provisions of President Trump's election overhaul bill.

(Image credit: Anna Moneymaker)

]]>
+ Eric McDaniel +
+ + In South Dakota, public media endures a year after federal funding was wiped out + We check in on one state public broadcasting network a year after President Trump signed a law ending federal funding of public media. + Wed, 22 Jul 2026 17:21:35 -0400 + https://www.npr.org/2026/07/22/nx-s1-5903146/in-south-dakota-public-media-endures-a-year-after-federal-funding-was-wiped-out + https://www.npr.org/2026/07/22/nx-s1-5903146/in-south-dakota-public-media-endures-a-year-after-federal-funding-was-wiped-out + We check in on one state public broadcasting network a year after President Trump signed a law ending federal funding of public media.

]]>
+ David Folkenflik +
+ + Trump administration signs commercial nuclear deal with Saudi Arabia + The agreement gives American companies priority access to nuclear reactors and fuel to Saudi Arabia. It's expected to last decades and be worth billions of dollars. + Wed, 22 Jul 2026 17:17:24 -0400 + https://www.npr.org/2026/07/22/nx-s1-5903293/trump-saudi-arabia-nuclear-deal + https://www.npr.org/2026/07/22/nx-s1-5903293/trump-saudi-arabia-nuclear-deal +

The agreement gives American companies priority access to nuclear reactors and fuel to Saudi Arabia. It's expected to last decades and be worth billions of dollars.

(Image credit: Andrew Harnik)

]]>
+ Shannon Bond +
+ + What to know about Ukraine's military shakeup + After a week of nationwide protests over the direction of Ukraine's military strategy in the ongoing war with Russia, the Ukrainian military has a new commander, Mykhailo Drapatyi. Here's what to know. + Wed, 22 Jul 2026 14:01:18 -0400 + https://www.npr.org/2026/07/22/g-s1-134813/ukraine-military-shakeup-zelenskyy + https://www.npr.org/2026/07/22/g-s1-134813/ukraine-military-shakeup-zelenskyy +

After a week of nationwide protests over the direction of Ukraine's military strategy in the ongoing war with Russia, the Ukrainian military has a new commander, Mykhailo Drapatyi. Here's what to know.

(Image credit: Paula Bronstein for NPR)

]]>
+ Joanna Kakissis +
+ + A strange transmissible cancer is spreading through the catfish in this lake + For more than a decade, there's been something fishy going on in a lake that straddles Vermont and the province of Quebec. It involves cancer, a kind of catfish, and — possibly — clues about how tumors metastasize. + Wed, 22 Jul 2026 13:33:15 -0400 + https://www.npr.org/2026/07/22/nx-s1-5901135/transmissible-cancer-catfish-melanoma-tumors + https://www.npr.org/2026/07/22/nx-s1-5901135/transmissible-cancer-catfish-melanoma-tumors + Nature describing the first known transmissible cancer in fish.'/>

For more than a decade, there's been something fishy going on in a lake that straddles Vermont and the province of Quebec. It involves cancer, a kind of catfish, and — possibly — clues about how tumors metastasize.

(Image credit: Joshua Brown)

]]>
+ Ari Daniel +
+ + In pursuit of 'Instagram face,' are we losing the imperfections that make us human? + Plastic surgery is becoming so normalized and undetectable, it's changing our relationship to reality. <em>The New Yorker</em> staff writer Jia Tolentino considers how beauty standards have dovetailed with AI. + Wed, 22 Jul 2026 13:11:19 -0400 + https://www.npr.org/2026/07/22/nx-s1-5902070/jia-tolentino-instagram-face-plastic-surgery + https://www.npr.org/2026/07/22/nx-s1-5902070/jia-tolentino-instagram-face-plastic-surgery +

Plastic surgery is becoming so normalized and undetectable, it's changing our relationship to reality. The New Yorker staff writer Jia Tolentino considers how beauty standards have dovetailed with AI.

]]>
+ Tonya Mosley +
+ + People are watching the Reflecting Pool like reality TV. What does that say about us? + It's not just watching paint dry. The twists and turns of the Reflecting Pool repairs, originally a two-week project, have kept bloggers and viewers busy all summer. + Wed, 22 Jul 2026 11:41:39 -0400 + https://www.npr.org/2026/07/22/nx-s1-5897383/dc-reflecting-pool-saga-influencers + https://www.npr.org/2026/07/22/nx-s1-5897383/dc-reflecting-pool-saga-influencers +

It's not just watching paint dry. The twists and turns of the Reflecting Pool repairs, originally a two-week project, have kept bloggers and viewers busy all summer.

(Image credit: Jackie Lay)

]]>
+ Rachel Treisman +
+ + Independent autopsy 'inconclusive' on cause of death for 18-year-old Nolan Wells in Mississippi + An independent autopsy of the body of Nolan Wells shows that the cause of death is undetermined pending an investigation, according to attorney Ben Crump. + Wed, 22 Jul 2026 10:48:26 -0400 + https://www.npr.org/2026/07/22/nx-s1-5902909/independent-autopsy-inconclusive-on-cause-of-death-for-18-year-old-nolan-wells-in-mississippi + https://www.npr.org/2026/07/22/nx-s1-5902909/independent-autopsy-inconclusive-on-cause-of-death-for-18-year-old-nolan-wells-in-mississippi +

An independent autopsy of the body of Nolan Wells shows that the cause of death is undetermined pending an investigation, according to attorney Ben Crump.

(Image credit: Gerald Herbert)

]]>
+ Brian Mann +
+ + Greetings from Jerusalem, whose holy sites host some of the oldest colonies of nesting swifts + The scythe-winged birds have nested in the cracks of the Western Wall and other holy sites in Jerusalem for thousands of years. + Wed, 22 Jul 2026 09:45:35 -0400 + https://www.npr.org/2026/07/22/g-s1-134756/jerusalem-swifts-birds-nesting-western-wall + https://www.npr.org/2026/07/22/g-s1-134756/jerusalem-swifts-birds-nesting-western-wall +

The scythe-winged birds have nested in the cracks of the Western Wall and other holy sites in Jerusalem for thousands of years.

]]>
+ Ruth Sherlock +
+ + Trump to attend dignified transfer of fallen soldiers. And, Hegseth testifies on Iran + Trump will attend the dignified transfer of U.S. service members killed in the Middle East. And, Pete Hegseth is requesting billions from Congress to help with the rising cost of the war in Iran. + Wed, 22 Jul 2026 07:24:22 -0400 + https://www.npr.org/2026/07/22/g-s1-134896/up-first-newsletter-trump-iran-war-pete-hegseth-arizona-primaries + https://www.npr.org/2026/07/22/g-s1-134896/up-first-newsletter-trump-iran-war-pete-hegseth-arizona-primaries +

Trump will attend the dignified transfer of U.S. service members killed in the Middle East. And, Pete Hegseth is requesting billions from Congress to help with the rising cost of the war in Iran.

(Image credit: Saul Loeb)

]]>
+ Brittney Melton +
+
+
\ No newline at end of file diff --git a/tests/corpus/rss/npr/expect.json b/tests/corpus/rss/npr/expect.json new file mode 100644 index 0000000..c741c28 --- /dev/null +++ b/tests/corpus/rss/npr/expect.json @@ -0,0 +1,7 @@ +{ + "type": "rss", + "version": "2.0", + "title": "NPR Topics: News", + "items": 10, + "first_item_title": "House passes Pentagon funding and limits on stock trades in a last dash before recess" +} diff --git a/tests/corpus/rss/xkcd_rss/data.xml b/tests/corpus/rss/xkcd_rss/data.xml new file mode 100644 index 0000000..ddd9e47 --- /dev/null +++ b/tests/corpus/rss/xkcd_rss/data.xml @@ -0,0 +1,2 @@ + +xkcd.comhttps://xkcd.com/xkcd.com: A webcomic of romance and math humor.enCalibration Nobelhttps://xkcd.com/3275/<img src="https://imgs.xkcd.com/comics/calibration_nobel.png" title="We would like to once again apologize to Dr. Jones for last year's mistaken announcement. We should really have double-checked the envelope for this award in particular." alt="We would like to once again apologize to Dr. Jones for last year's mistaken announcement. We should really have double-checked the envelope for this award in particular." />Wed, 22 Jul 2026 04:00:00 -0000https://xkcd.com/3275/Arthurian Connectorhttps://xkcd.com/3274/<img src="https://imgs.xkcd.com/comics/arthurian_connector.png" title="Most coffee shops have a descendant of Sophia of Hanover on staff for this, but just as I was about to ask for help, a previously unknown heir of Uther Pendragon who was ordering a muffin tripped on my laptop cord." alt="Most coffee shops have a descendant of Sophia of Hanover on staff for this, but just as I was about to ask for help, a previously unknown heir of Uther Pendragon who was ordering a muffin tripped on my laptop cord." />Mon, 20 Jul 2026 04:00:00 -0000https://xkcd.com/3274/Latitude and Longitudehttps://xkcd.com/3273/<img src="https://imgs.xkcd.com/comics/latitude_and_longitude.png" title="NGS and IERS are complaining that they left CLEAR instructions to set the washing machine to WGS84 (G2296) instead of WGS84 (G730)." alt="NGS and IERS are complaining that they left CLEAR instructions to set the washing machine to WGS84 (G2296) instead of WGS84 (G730)." />Fri, 17 Jul 2026 04:00:00 -0000https://xkcd.com/3273/Time Changehttps://xkcd.com/3272/<img src="https://imgs.xkcd.com/comics/time_change.png" title="All discussions of daylight saving time policy are doomed by a mix of contradictory, inconsistent, and impossible preferences, which is why I think the only thing we can really hope to do is to make it worse." alt="All discussions of daylight saving time policy are doomed by a mix of contradictory, inconsistent, and impossible preferences, which is why I think the only thing we can really hope to do is to make it worse." />Wed, 15 Jul 2026 04:00:00 -0000https://xkcd.com/3272/ \ No newline at end of file diff --git a/tests/corpus/rss/xkcd_rss/expect.json b/tests/corpus/rss/xkcd_rss/expect.json new file mode 100644 index 0000000..28452a2 --- /dev/null +++ b/tests/corpus/rss/xkcd_rss/expect.json @@ -0,0 +1,7 @@ +{ + "type": "rss", + "version": "2.0", + "title": "xkcd.com", + "items": 4, + "first_item_title": "Calibration Nobel" +} diff --git a/tests/test_corpus.py b/tests/test_corpus.py new file mode 100644 index 0000000..298380d --- /dev/null +++ b/tests/test_corpus.py @@ -0,0 +1,110 @@ +""" +Real-world feed corpus tests. + +Feeds in tests/corpus/ were captured from the wild; every expect.json was written by +inspecting the raw XML (see tests/corpus/README.md), so the parser is checked against +the documents themselves, not against its own output. +""" + +import json +import re +from pathlib import Path + +import pytest + +from rss_parser import FeedType, PodcastParser, detect_feed_type, parse +from rss_parser.models.atom import Atom +from rss_parser.models.rdf import RDF +from rss_parser.models.rss import RSS + +CORPUS_DIR = Path(__file__).parent / "corpus" + +MODEL_BY_TYPE = {"rss": RSS, "atom": Atom, "rdf": RDF} +FEED_TYPE_BY_TYPE = {"rss": FeedType.RSS, "atom": FeedType.ATOM, "rdf": FeedType.RDF} + + +def iter_corpus(): + for kind_dir in sorted(d for d in CORPUS_DIR.iterdir() if d.is_dir()): + for feed_dir in sorted(kind_dir.iterdir()): + if (feed_dir / "data.xml").is_file(): + yield kind_dir.name, feed_dir + + +def load_feed(feed_dir: Path, expect: dict) -> str: + return (feed_dir / "data.xml").read_bytes().decode(expect.get("encoding", "utf-8")) + + +def get_items(feed): + if isinstance(feed, RSS): + return feed.channel.content.items + if isinstance(feed, Atom): + return feed.feed.content.entries + return feed.items + + +CORPUS = list(iter_corpus()) +CORPUS_IDS = [f"{kind}/{feed_dir.name}" for kind, feed_dir in CORPUS] + + +@pytest.mark.parametrize(("kind", "feed_dir"), CORPUS, ids=CORPUS_IDS) +def test_corpus_feed(kind, feed_dir): + expect = json.loads((feed_dir / "expect.json").read_text(encoding="utf-8")) + data = load_feed(feed_dir, expect) + + # Detection matches the root element of the document + assert detect_feed_type(data) == FEED_TYPE_BY_TYPE[expect["type"]] + + feed = parse(data) + assert isinstance(feed, MODEL_BY_TYPE[expect["type"]]) + + if "version" in expect: + assert feed.version.content == expect["version"] + + # Facts derived from the raw XML + channel = feed.channel.content if hasattr(feed, "channel") else feed.feed.content + assert str(channel.title) == expect["title"] + + items = get_items(feed) + assert len(items) == expect["items"] + assert str(items[0].content.title) == expect["first_item_title"] + + +@pytest.mark.parametrize(("kind", "feed_dir"), CORPUS, ids=CORPUS_IDS) +def test_corpus_feed_serializes(kind, feed_dir): + """Every corpus feed must survive both serialization paths.""" + expect = json.loads((feed_dir / "expect.json").read_text(encoding="utf-8")) + feed = parse(load_feed(feed_dir, expect)) + + assert json.loads(feed.model_dump_json()) + assert json.loads(feed.json_plain()) + + +@pytest.mark.parametrize( + ("kind", "feed_dir"), + [(kind, feed_dir) for kind, feed_dir in CORPUS if kind == "podcast"], + ids=[i for i in CORPUS_IDS if i.startswith("podcast/")], +) +def test_corpus_podcast_itunes_fields(kind, feed_dir): + expect = json.loads((feed_dir / "expect.json").read_text(encoding="utf-8")) + podcast = PodcastParser.parse(load_feed(feed_dir, expect)) + + assert str(podcast.channel.content.itunes_author) == expect["itunes_author"] + + +def test_corpus_expectations_are_complete(): + """Every corpus feed dir must carry a reviewed expect.json with the required facts.""" + assert CORPUS, "corpus is empty" + for _, feed_dir in CORPUS: + expect = json.loads((feed_dir / "expect.json").read_text(encoding="utf-8")) + missing = {"type", "title", "items", "first_item_title"} - set(expect) + assert not missing, f"{feed_dir} expect.json is missing {missing}" + + +def test_corpus_titles_match_raw_xml(): + """Cross-check: the expected channel title literally appears in the raw document.""" + for _, feed_dir in CORPUS: + expect = json.loads((feed_dir / "expect.json").read_text(encoding="utf-8")) + raw = load_feed(feed_dir, expect) + # Titles may be entity-encoded in the XML; normalize apostrophes and ampersands + pattern = re.escape(expect["title"]).replace("'", "(?:'|'|�?39;)").replace("&", "(?:&|&)") + assert re.search(pattern, raw), f"{feed_dir}: expected title not found in raw XML" diff --git a/tests/test_dates.py b/tests/test_dates.py new file mode 100644 index 0000000..8ecfb11 --- /dev/null +++ b/tests/test_dates.py @@ -0,0 +1,41 @@ +from datetime import datetime, timezone + +from rss_parser import RSSParser +from rss_parser.models.types.date import validate_dt_or_str + +ITEM_TEMPLATE = ( + "TLD" + "t{date}" +) + + +def parse_pub_date(date: str): + rss = RSSParser.parse(ITEM_TEMPLATE.format(date=date)) + return rss.channel.content.items[0].content.pub_date.content + + +class TestDateParsing: + def test_rfc_822(self): + parsed = parse_pub_date("Sat, 07 Sep 2002 00:00:01 GMT") + + assert parsed == datetime(2002, 9, 7, 0, 0, 1, tzinfo=timezone.utc) + + def test_rfc_822_with_two_digit_year(self): + parsed = parse_pub_date("Sat, 07 Sep 02 00:00:01 GMT") + + assert parsed == datetime(2002, 9, 7, 0, 0, 1, tzinfo=timezone.utc) + + def test_iso_8601(self): + parsed = parse_pub_date("2002-09-07T00:00:01+00:00") + + assert parsed == datetime(2002, 9, 7, 0, 0, 1, tzinfo=timezone.utc) + + def test_unparseable_date_is_kept_as_string(self): + """Dates in the wild are too messy to reject a whole feed over one of them.""" + parsed = parse_pub_date("someday, probably") + + assert parsed == "someday, probably" + + def test_datetime_instance_passes_through(self): + now = datetime(2020, 1, 1, tzinfo=timezone.utc) + assert validate_dt_or_str(now) is now diff --git a/tests/test_properties.py b/tests/test_properties.py new file mode 100644 index 0000000..7dfa347 --- /dev/null +++ b/tests/test_properties.py @@ -0,0 +1,80 @@ +""" +Property-based tests: invariants that must hold for *any* feed, not just the ones we thought of. +""" + +from xml.sax.saxutils import escape + +from hypothesis import given, settings +from hypothesis import strategies as st + +from rss_parser import RSSParser +from rss_parser.models.rss import RSS + +# XML 1.0 can't carry control characters, and xmltodict strips surrounding whitespace, +# so generate stripped, control-free, non-empty text. +xml_text = ( + st.text(alphabet=st.characters(blacklist_categories=("Cc", "Cs")), min_size=1, max_size=50) + .map(str.strip) + .filter(bool) +) + +tag_names = st.text(alphabet="abcdefghijklmnopqrstuvwxyz", min_size=1, max_size=10) + + +def render_rss(channel_title, item_titles, extra_channel_xml=""): + items = "".join(f"{escape(t)}" for t in item_titles) + return ( + f'' + f"{escape(channel_title)}" + f"http://example.com" + f"D" + f"{extra_channel_xml}{items}" + f"" + ) + + +@settings(max_examples=50) +@given(channel_title=xml_text, item_titles=st.lists(xml_text, min_size=0, max_size=5)) +def test_text_content_round_trips(channel_title, item_titles): + """Any escaped text placed in a tag comes back out unchanged.""" + rss = RSSParser.parse(render_rss(channel_title, item_titles)) + + assert rss.channel.content.title.content == channel_title + assert [item.content.title.content for item in rss.channel.content.items] == item_titles + + +@settings(max_examples=50) +@given(count=st.integers(min_value=0, max_value=5)) +def test_items_are_always_a_list(count): + """The xmltodict one-element-isn't-a-list quirk must never leak through.""" + rss = RSSParser.parse(render_rss("T", [f"item {i}" for i in range(count)])) + + items = rss.channel.content.items + assert isinstance(items, list) + assert len(items) == count + + +@settings(max_examples=50) +@given(name=tag_names, value=xml_text) +def test_unknown_namespaced_tags_are_never_dropped(name, value): + key = f"x:{name}" + rss = RSSParser.parse(render_rss("T", [], f"<{key}>{escape(value)}")) + + assert rss.channel.content.model_extra[key] == value + + +@settings(max_examples=50) +@given(channel_title=xml_text, item_titles=st.lists(xml_text, min_size=1, max_size=3)) +def test_dump_validate_round_trip_is_idempotent(channel_title, item_titles): + """model_validate(model_dump()) must reproduce the exact same model.""" + rss = RSSParser.parse(render_rss(channel_title, item_titles)) + + assert RSS.model_validate(rss.model_dump()) == rss + + +@settings(max_examples=50) +@given(value=xml_text) +def test_attributes_survive(value): + rss = RSSParser.parse(render_rss("T", [], f'')) + + assert rss.channel.content.categories[0].attributes == {"some_attr": value} diff --git a/tests/test_spec_compliance.py b/tests/test_spec_compliance.py index 33cbf32..b9fc20d 100644 --- a/tests/test_spec_compliance.py +++ b/tests/test_spec_compliance.py @@ -74,6 +74,11 @@ def test_rating_is_a_string(self): assert channel.rating.content.startswith("(PICS-1.1") + def test_self_closing_list_tag_is_an_empty_list(self): + channel = parse_channel("") + + assert channel.categories == [] + def test_ttl_is_an_int(self): channel = parse_channel("60") diff --git a/uv.lock b/uv.lock index 7a8aabd..3f3ad16 100644 --- a/uv.lock +++ b/uv.lock @@ -67,6 +67,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d4/2b/04b8a15f3a1c77bc79ddf5c73875327f34b4fa75982df2b76e45e402d364/asttokens-3.0.2-py3-none-any.whl", hash = "sha256:9da13157f5b28becde0bd374fc677dcd3c290614264eff096f167c469cd9f933", size = 28702, upload-time = "2026-07-12T03:31:47.542Z" }, ] +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + [[package]] name = "babel" version = "2.18.0" @@ -382,6 +391,233 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli" }, +] + +[[package]] +name = "coverage" +version = "7.15.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/d0/55fe630f4cf94e3fcba868240fad8c8cdd1f764e2a932f8926347e6ec4cd/coverage-7.15.2.tar.gz", hash = "sha256:3df60dc267f0a2ca23cb7a9ab1109c62b9335ffbf519fcfe167157c28c09b81d", size = 927741, upload-time = "2026-07-15T18:56:19.558Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/03/060ce69008ac97bbc01b1411b3e55b61f6f015659400b46749b662107831/coverage-7.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b5bd92ff1ec22e535eab0de75fa6db021992791f461a2aceb7822c625a1187d", size = 221284, upload-time = "2026-07-15T18:53:29.52Z" }, + { url = "https://files.pythonhosted.org/packages/fc/a3/d936e8b53edd9684100a6aefaf3fcabaa54728fe33324436c8d279c047aa/coverage-7.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:44826758cfe73fcd0e6af5deb4ba6d5417cc1d13df3acb35c93484a11160f846", size = 221799, upload-time = "2026-07-15T18:53:31.708Z" }, + { url = "https://files.pythonhosted.org/packages/ae/a3/ca234b06aec7ee28226f11d39a696b4481fe5eddfce8e03bf39979bb8ffb/coverage-7.15.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09f5c6ec5901f667bd97dd140b5b9a2586b10efec66f46fb1e6d8135f8b95bdf", size = 248544, upload-time = "2026-07-15T18:53:33.212Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/dda79527bb7573ba91828b2fb91b3105d87378d6a2749ca0c0924ce0addd/coverage-7.15.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1d16e3a7104ea84f03e614611b3edbf6fb6892554b3ab0fe7fbb3f2b2ef04376", size = 250374, upload-time = "2026-07-15T18:53:34.683Z" }, + { url = "https://files.pythonhosted.org/packages/67/c6/c33755a34572f81f49a8c0cdf6b622f35ccb3238b136e1909daf0cdd4319/coverage-7.15.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d46e62cb35d91e6e2589fda6d28074426b0e276422b5d2ebef2c6b11dc60dbfd", size = 252239, upload-time = "2026-07-15T18:53:36.205Z" }, + { url = "https://files.pythonhosted.org/packages/b9/6f/dc341741b375be53a5baeee5b4bf0f0e525d38caed428f7932d23bb7bcb1/coverage-7.15.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dfd3db045e95960ae3683059571e597fda7cc610106a8916f77c5839048c1deb", size = 254150, upload-time = "2026-07-15T18:53:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8d/966a18a5b195cb4e77b14c53f5f3dce22b5da05e6de7fafd1e08f2d2067a/coverage-7.15.2-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:affd532502d34c0472d0cdb181325c89f1d2c44992fef0c17e88e7b1576259a1", size = 249234, upload-time = "2026-07-15T18:53:39.394Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8b/8b2e367496ab48484d48e79984fec76cdc1b7cb5d3a00ee799a5602e3ec9/coverage-7.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d17d7512151fedfcc64c1821a8977fc9be0dbf495754669afcab7b57abc98ae9", size = 250276, upload-time = "2026-07-15T18:53:41.027Z" }, + { url = "https://files.pythonhosted.org/packages/63/92/1199318a200eb6c8c6ce0192c892c8710ac791abbe0f35099294620bbfda/coverage-7.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e26ff680768b8095e8874aabe0e9d3a47a2a9f176a8340d05f8604c56457c23a", size = 248283, upload-time = "2026-07-15T18:53:42.557Z" }, + { url = "https://files.pythonhosted.org/packages/56/da/be284a55c5619bda891a89c27dfd59324a2c6a14d755cf6aac6960ceebeb/coverage-7.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e8f27131dc7cd53de2c137dd207b3720919320b3c20d499dc30aa9ee6173287", size = 252093, upload-time = "2026-07-15T18:53:44.271Z" }, + { url = "https://files.pythonhosted.org/packages/d4/53/ee112da833ddd77b73c6d781a98029b45b584b136615b4900ed0569f887e/coverage-7.15.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:728a33676d4c3f0db977990a4bd421dcaa3be3e53b5b6273036fff6666008e89", size = 248552, upload-time = "2026-07-15T18:53:45.7Z" }, + { url = "https://files.pythonhosted.org/packages/82/6a/802cfc802e9113494c80bf3f284cd4d72faeb1f24e244f61046af364f2ca/coverage-7.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29c052f7c83ccfcc5c577eaae025d2e4a9bb80daf03c0ac31c996e83b000ce88", size = 249154, upload-time = "2026-07-15T18:53:47.256Z" }, + { url = "https://files.pythonhosted.org/packages/2c/65/529808e91d651147edae408fd9e894abc3b8cad7f3e594bbc36719a3e13a/coverage-7.15.2-cp310-cp310-win32.whl", hash = "sha256:1268ac8fb9ddcd783d3948dbabaf80a5d53bfdaa0575e873e2139a692f797443", size = 223334, upload-time = "2026-07-15T18:53:48.768Z" }, + { url = "https://files.pythonhosted.org/packages/68/0f/0e1829d7001130876dfbc0b4e1c737ea7c155b809e3e4a98a0aa268e2369/coverage-7.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:9f4432898c4bf2fba0435bbe35dd4437d7264565e5a88a21f5b49d8662a6b629", size = 223959, upload-time = "2026-07-15T18:53:50.429Z" }, + { url = "https://files.pythonhosted.org/packages/7d/3a/54536704f507d4573bf9161c4d0dd3dd59b6d85e48c664e901b6844d8e33/coverage-7.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f1ec6f304b156669cfde653b4e9a953f5de87e247ea02ac599bce0ab2744036", size = 221414, upload-time = "2026-07-15T18:53:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/b6/d9/8ba925d29743e3577b21e4d8c11a702b76bc93c41e7fdfd1177af63d4b8d/coverage-7.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d3361879d736f469f45723c11ea1a5bbdaf1f6928f0e632c940378b5aa9b660", size = 221913, upload-time = "2026-07-15T18:53:53.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/a855f3aa0187f2b431ade4e4791b77b56282cfb5d201c83ec26a31b5b36a/coverage-7.15.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c6a98d698f9e2c8008d0370ec7fc452ebfcc530002ae2d0061170d768b992589", size = 252332, upload-time = "2026-07-15T18:53:55.467Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d3/13ac97b4370640ba3452fc8559b06cc2f479ce3ba4a0b632a73e44c38a7d/coverage-7.15.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d50dd325e18ec25bfcc10cd7f99b04df1ab9ec76b0918c260e60817ad0643dee", size = 254243, upload-time = "2026-07-15T18:53:57.055Z" }, + { url = "https://files.pythonhosted.org/packages/88/83/5eca144942d8d0659d3f55176517f4a59cdc65eefd17146a0770935a3ebd/coverage-7.15.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67d7602480a47bdf5b675635403625553ebaa70d5a62a657c035149fd401cea0", size = 256352, upload-time = "2026-07-15T18:53:58.83Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ba/d3db2e01a50fc88cdb4c0f19542bcf6f61489e34dc9aa3538413e2459a38/coverage-7.15.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cee0f89f4767a6057c8fbf168f8135f18be651300496086bd873e3189fed0487", size = 258313, upload-time = "2026-07-15T18:54:00.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/b3/aba83416e9177df28e5186d856c19158c59fc0e7e814aaa61a4a2354ad1b/coverage-7.15.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a29ec5305a7335aacee2d799e3422e91e1c8a12474986e2b3b07e315c91be82f", size = 252449, upload-time = "2026-07-15T18:54:02.456Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a5/4b00ecac0194431ab451b0f6710f8e2517d04cef60f821b14dec4637d575/coverage-7.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:48ccc6395958eda89093ecdc35644c86f23a8b23a7f4d44958812b721aad67c1", size = 254043, upload-time = "2026-07-15T18:54:04.072Z" }, + { url = "https://files.pythonhosted.org/packages/75/b6/cfa209b4313ee7f1b34da47efcd789ea51c024ad35af390e00f5a3c10a2e/coverage-7.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:81f382c5a94b434ec1f6da607edb904c76d7212e618cd4d1bc9f97bed4120ef5", size = 252107, upload-time = "2026-07-15T18:54:06.745Z" }, + { url = "https://files.pythonhosted.org/packages/36/67/e8cac5a6954038c98d7fe7eb9802afe7ab3ecb637bb7cc00e69b4148b56d/coverage-7.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bbc808daf4f5cd567af8075ecc72d21c6dfef9a254709a621a84c217c935ebc0", size = 255873, upload-time = "2026-07-15T18:54:08.48Z" }, + { url = "https://files.pythonhosted.org/packages/2c/92/395cca9f330a86c3fe3471d73e2c102116c4c58fdc619dbbc125c6e93a54/coverage-7.15.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a4c46b247b5d4b78f613bd89fea926d32b25c6cc61a50bd1e99ba310348f3dad", size = 251826, upload-time = "2026-07-15T18:54:10.083Z" }, + { url = "https://files.pythonhosted.org/packages/51/60/3e91b20295439652424f426b7086ec5bf4fbe3f604c73eda22b986c4fd6b/coverage-7.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:094dd37f3ef7b2da8b068b583d1f4c40f91c65197e16c52a71962d5d537fc5db", size = 252735, upload-time = "2026-07-15T18:54:11.878Z" }, + { url = "https://files.pythonhosted.org/packages/a5/eb/8c07839005e5e3c6b3877d3a6e2a80ce766589f31dd2b6882b78d59a7b8c/coverage-7.15.2-cp311-cp311-win32.whl", hash = "sha256:a63b9e190711134d581c4d703df5df09851b1acf99792c7aacbbe9f41f0283c9", size = 223500, upload-time = "2026-07-15T18:54:13.525Z" }, + { url = "https://files.pythonhosted.org/packages/2e/98/59d83c257cd59f0fbaf9d9ddb26b744a576760dfd1ae16e516408894a02b/coverage-7.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:8bb9f4b4279187560796a4cdaca3b0a93dd97e48ee667df005f4ed9a97403688", size = 223973, upload-time = "2026-07-15T18:54:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/ea/09/2d285c8bef5c4f695d120c1c96dc11715638aa8e134069f210bb6a62a9fe/coverage-7.15.2-cp311-cp311-win_arm64.whl", hash = "sha256:8c726b232659cbd2ae57ade46509eb068c9bd7a06df9fcbff6fe484870006934", size = 223519, upload-time = "2026-07-15T18:54:16.803Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/eb5bf42e531611a9f8d272556b1ed4de503f84a91413584094487cf69f8f/coverage-7.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1adac78e5abc7c5438f7a209c9ca69d06542f0bf481d728b6989ea80b813fdf9", size = 221587, upload-time = "2026-07-15T18:54:18.439Z" }, + { url = "https://files.pythonhosted.org/packages/06/d1/da99af464c335d4e023a6efcd7ec30f63b88a43c93745154ab74ffb31cea/coverage-7.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b868acc62aa5de3be7a9d05c2333bf8359ca987e43f9cb30ff8fbda6a024ab73", size = 221943, upload-time = "2026-07-15T18:54:20.062Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8a/13c42723d61ca447eafa18732e8141dd6a63f2732e1c7e1502c182dd88d7/coverage-7.15.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6f6966fc30e6f06ca8f98fb0ce51eda6b111b3ee8d066a8b1ec9e77fa06ab55d", size = 253450, upload-time = "2026-07-15T18:54:21.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/99021303f98fbdcb63504b4d07bea4cc025b9b2dd907c4f07c85d50a0dab/coverage-7.15.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:68af907f595ab01a78f794932ff3bdf929c316d3000810d38dbc247129e26f8b", size = 256187, upload-time = "2026-07-15T18:54:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/f9/a8/fd503715ed6ca9c5d742923aa5209257340b367a867b2ced0c7d4ba8a0b9/coverage-7.15.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afa29e2eff3d5729267e2cb2fd4ce9d61c952932fb2694e34ccb5d9540c6a296", size = 257301, upload-time = "2026-07-15T18:54:25.183Z" }, + { url = "https://files.pythonhosted.org/packages/da/40/3f4b8fb409810036ebc2857d36adc0498c6e957b5df0290c5036b2e143f1/coverage-7.15.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bbf44513ceb1589e31948e20eafbde9deaface90e1a1afa5f5f77b4423d17ce6", size = 259562, upload-time = "2026-07-15T18:54:27.204Z" }, + { url = "https://files.pythonhosted.org/packages/0b/8a/9bdffbef47db77cce3d6b02a28f7e919b19f0106c4b080c2c2246040f885/coverage-7.15.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9deddf09eecb717b7f980414b43d90a5b22ff3967d2949ab29cb0aa83d9e9098", size = 253841, upload-time = "2026-07-15T18:54:29.134Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/9031efde019d31a06646261fce6dfc5c3c74e951e27a71e5c9a424563178/coverage-7.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ae901f7e55ba405c84ee1cab3d3e962e4e871e4a2bcb9c90911adbd69b42ac5a", size = 255221, upload-time = "2026-07-15T18:54:31.142Z" }, + { url = "https://files.pythonhosted.org/packages/56/db/787acde872389fc84a9ef9d8cd1ccc658e391ab4cb5b28092a714426a394/coverage-7.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a0f47002c6eeb7c280228467a4cb0cc15ca2103a8421b986b2d3ec04a0f9bd8b", size = 253366, upload-time = "2026-07-15T18:54:32.886Z" }, + { url = "https://files.pythonhosted.org/packages/2f/9b/6f57bc4b93c842eef1695f8cdaf2318e35e7ba54f5ba80d84be213ab7858/coverage-7.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1cd7a5beb7af3e864a13b1f0fb26efd3695da43ef0daf71e586adfffaf34d5b2", size = 257434, upload-time = "2026-07-15T18:54:34.7Z" }, + { url = "https://files.pythonhosted.org/packages/88/26/b3186a21b2acc83e451118978905c81c7072c3333707804db09a78c096a2/coverage-7.15.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:97a5c5457a9fb1d6c4e06cfb5dc835871fbfb6a6a51addc9e925bdeff5ef7440", size = 252935, upload-time = "2026-07-15T18:54:36.548Z" }, + { url = "https://files.pythonhosted.org/packages/20/c2/c9f3376b2e717ea69ed7a6e9a5fcab968fb0b290db6cf4bd9a1fc7541b75/coverage-7.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0901cfe6c13bcd2302da4f83e884555d2a22bda6e4c476f09ef204ba20ca536e", size = 254807, upload-time = "2026-07-15T18:54:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/f0/e1/dfc15401f4a8aaeb486e1ba3e9e3c40522a6e38bd0ecf0b3f29cb8082957/coverage-7.15.2-cp312-cp312-win32.whl", hash = "sha256:b171bdd71cb7ff792bf32e376173b0ace7e7963e7e57c58dfc42063a6a7174cd", size = 223641, upload-time = "2026-07-15T18:54:40.103Z" }, + { url = "https://files.pythonhosted.org/packages/91/40/81b6d809d320cd366ec5bdf8176575e897dcb8efe7fb4b489ef9e93e4d13/coverage-7.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:582edc45c2040543fef83341be23c43024a3ab3ae0c2d8bc498a06282905ad40", size = 224172, upload-time = "2026-07-15T18:54:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/ef/28/9f14ec438149f7de557f45518f09b4a7917b795cc37083aa7db482693f8c/coverage-7.15.2-cp312-cp312-win_arm64.whl", hash = "sha256:a638db90c61cd219aeee65e83a24fdaa57269a741ae0cf773309208ac862cee3", size = 223556, upload-time = "2026-07-15T18:54:43.674Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d5/f8c838e6b7282976f7c918884b792df7a0c42c5bba5d99c60ad2d221d56d/coverage-7.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1121caa19159a38b5463eaae4b1e1fde81e525b15ecc5e000cd5b1a108f743a8", size = 221606, upload-time = "2026-07-15T18:54:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/bf/37/97c926376364f66298cc44893b89cdf17b8bc406376497c4061ae4b8a8ff/coverage-7.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a300c6934e0989c327b9e8a1e110329da4641149f872bbe9f70168be66da76c1", size = 221982, upload-time = "2026-07-15T18:54:47.341Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/a36050a6e83c2135ee0776f452ca3948224befc6d7f26acecc082d0c106a/coverage-7.15.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2617f8799d268fabdeef42a7e89ac3a23e1deee9025427db2df970f99a89a578", size = 252972, upload-time = "2026-07-15T18:54:49.2Z" }, + { url = "https://files.pythonhosted.org/packages/31/d3/06b5f1daf95f0f15ab05bd75f26ba5f3c8b33d0bb72f3aaa3cf41d1bad3a/coverage-7.15.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7dc2950a2992cd676d35c20ae63522836deeb034f08874699d14068710af3dc1", size = 255569, upload-time = "2026-07-15T18:54:51.098Z" }, + { url = "https://files.pythonhosted.org/packages/81/1c/9afb3f8de2b8d36960391c48559a2e3ff96594b58099f115921549ea8d0d/coverage-7.15.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e36686f7a442185db2400b3df171aac520869faf9deb59df687d28659eda2a6", size = 256806, upload-time = "2026-07-15T18:54:53.145Z" }, + { url = "https://files.pythonhosted.org/packages/64/d8/b989f96061a5e32d82fddd1b1b9ff48a7c8f8ae7606f0e80fd9de54b1e33/coverage-7.15.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d29ca7bd67af6e12e74632d65f026eabc1364da5c254494cd914446a28a3ef7", size = 258936, upload-time = "2026-07-15T18:54:55.015Z" }, + { url = "https://files.pythonhosted.org/packages/b8/fa/f99771f5110457c7b511c1935ca49ddf288218eaa84322e028b9334146ae/coverage-7.15.2-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:db9c8438057e5b0f6a22a0af99c0c1d26b57fbbdbd1be5861ddb8f897fcc3a2d", size = 253178, upload-time = "2026-07-15T18:54:57.527Z" }, + { url = "https://files.pythonhosted.org/packages/f6/96/c098a6044d119c751ceede7be91035fa8310170ec24a6523aff72f0a5793/coverage-7.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:63022c4c8dec1d0342f05c3ede99842fe3d007689acc45e86f123a1746e4a026", size = 254934, upload-time = "2026-07-15T18:54:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a2/1457b3a7a50c8d77500103b97a046db863e2f59a1cf6d2f814595f349885/coverage-7.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6c0be82b4d4aa5b2704e08518e2252f3e3d110164bcca826816801052e48a7aa", size = 252898, upload-time = "2026-07-15T18:55:01.338Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0e/76958874c471ecfcdde0d2b2747bb2c61bdbf34a40636f4ce9db9923e643/coverage-7.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4510fb9cdf6bb02dfa6af0be4a534b8102d086e22e4a33f8836df663da3d660d", size = 257056, upload-time = "2026-07-15T18:55:03.243Z" }, + { url = "https://files.pythonhosted.org/packages/7c/7c/3d7c4e3bf58baa40327dc7edc2272b17cf02299366d52763db1b0ca1556a/coverage-7.15.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:42ec3d989421b174a2ab607c1539f24127ad362757b7f1c0c0d7a2993f7eb37b", size = 252718, upload-time = "2026-07-15T18:55:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/1cecffed9ce14fb25be9ba42d37b6bb61485c9a3ddd43cd3dde36b6087d8/coverage-7.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e8f91bce78e32343af184c3b7fa28fcf5a9e2641f4b6623d392038f804939188", size = 254490, upload-time = "2026-07-15T18:55:06.889Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2c/42984561bc7f4c045dca67516a0c50ee5ef8d84352dbeb5559dc86c4823e/coverage-7.15.2-cp313-cp313-win32.whl", hash = "sha256:434e68d531858205895eb0d74b73d20b84260de426387d53c422a5acda2cf050", size = 223647, upload-time = "2026-07-15T18:55:08.941Z" }, + { url = "https://files.pythonhosted.org/packages/41/9f/39c7c9245efc583beddf89a87683574e663ed93637f3afb6cd7b88405676/coverage-7.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:26c3b04a6377fd7c09800921fa934e3a17c0020439cd59df73e73ae1d4b6a78c", size = 224190, upload-time = "2026-07-15T18:55:10.789Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/3a2883cf8a213659280ef4b403059e17a9acaeb7fc7fd4105e1226ff2e6d/coverage-7.15.2-cp313-cp313-win_arm64.whl", hash = "sha256:3ed010aa1b69cda8e827aabfca9866216c980e2dca82ab9a78c5f83689964c8b", size = 223583, upload-time = "2026-07-15T18:55:12.678Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/aed265fd7a3551a394f36dfe41868aee709b7f95db4052205b4ad1563ac3/coverage-7.15.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:40f633c5c5fc783732f6312280122e859538fa24461235597c13d803ea9a108a", size = 221650, upload-time = "2026-07-15T18:55:14.527Z" }, + { url = "https://files.pythonhosted.org/packages/6b/2c/222ba12a545189017120f8eddfc1a0bd4616b47d5d4a8d99421edb2fe4c6/coverage-7.15.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:075560438765b7a2ef43bf7aa7758661b53d889df47f062a31bda6c1ade553a2", size = 221988, upload-time = "2026-07-15T18:55:16.674Z" }, + { url = "https://files.pythonhosted.org/packages/aa/38/304b5877ab46e6c290b4292cfcf3fe28245f0e5597cad7f6acc91fc7e0a4/coverage-7.15.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:25fd15dd40a0a2c51a500d664ca29053c09c3259d998407bf982b6e114696138", size = 253029, upload-time = "2026-07-15T18:55:18.856Z" }, + { url = "https://files.pythonhosted.org/packages/6c/58/821b533b8db9e44cf1d8a97bd525149ced40dde1d0093da02cb78e715244/coverage-7.15.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b9a6367e4aff723e8ee8190836836124284e8fcd4265e307c844010cfa074f3f", size = 255536, upload-time = "2026-07-15T18:55:21.027Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f2/7aa06604c389d32ea7f0a6a988359a7eafc3cd3f8e7bc2e88cd2fdf0b877/coverage-7.15.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9854ca62c152874b2060772503535be2e8f53f70b8aaa7686b094888d872f984", size = 256881, upload-time = "2026-07-15T18:55:23.125Z" }, + { url = "https://files.pythonhosted.org/packages/a2/4f/1ef342339c7916d0096bc5888cc0f653882cc7bc8f897d5cb89143287c9b/coverage-7.15.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:913b6c56e110da40e035bbd168353bf7aaa2544a5eaccea5d98a4629aac156c7", size = 259196, upload-time = "2026-07-15T18:55:25.099Z" }, + { url = "https://files.pythonhosted.org/packages/fe/f4/7ed055d7a9c5ec13b161773a115a5ccc6b0081d568c31fad830806306cc7/coverage-7.15.2-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:aaccad4129d735a8a4d526f26929894c9a4e8ef7034566f210b176749d6906e3", size = 253036, upload-time = "2026-07-15T18:55:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/14/79/ea82cca18c242a3a38b6c017da39726aa62dcb64aa635abf79b92009975c/coverage-7.15.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a164b50081fc7357331c4024ef4d17b78ba325f8380d05f5a69599a7e05257ee", size = 254887, upload-time = "2026-07-15T18:55:29.084Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ba/a136db3c0d9562b00e10b72540dbf3a33cd3bc5b95060c9308e247494623/coverage-7.15.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:bfd341ccf78128e72c094bc70cc25b3ef309c33c7c2c66ba3ed4309549e02de1", size = 252852, upload-time = "2026-07-15T18:55:31.184Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/ea334246b16b7d059953fad6fdefa11e33c68efbd3fe37b1098120a1fac2/coverage-7.15.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:1473b3ba8e7ee0f076117b1a72c23f579a2b9e2bb742f48a8d86ea27ca93f91a", size = 257128, upload-time = "2026-07-15T18:55:33.163Z" }, + { url = "https://files.pythonhosted.org/packages/ed/c3/074fb66d46d607855f710876b117cbda562c5ab08363528e78820449f937/coverage-7.15.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:17c432b5f73ad52ef46fb06019f6fa7c66ce381961cf0f7dfd1d3a4bd3a98145", size = 252668, upload-time = "2026-07-15T18:55:35.063Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c1/f620850ada9b36435921c9a3a8057013422b1d964eb4bf37fe138724d192/coverage-7.15.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:77f0ef5011df53a4bd1b35211ab122287f8d9b8d7aa1c4553e5c2deb24b1d446", size = 254325, upload-time = "2026-07-15T18:55:37.125Z" }, + { url = "https://files.pythonhosted.org/packages/cc/31/a729ca3689404493af82ef8e6ff70bd88bdda8da89aeef6ca9b387aeb2b4/coverage-7.15.2-cp314-cp314-win32.whl", hash = "sha256:f653e5d7248c1191ec988a85c72edeab46c3ff44f90639a4ed4874ec0be90243", size = 223844, upload-time = "2026-07-15T18:55:39.078Z" }, + { url = "https://files.pythonhosted.org/packages/c6/83/5d809dc808fb1698c671f3e372259bb9158e64b7ea526fc6ab7de64de9fe/coverage-7.15.2-cp314-cp314-win_amd64.whl", hash = "sha256:9911f31aad8906abe337c271343485cf20df5e70df5d2f57f9f136e7b55f26bc", size = 224331, upload-time = "2026-07-15T18:55:41.346Z" }, + { url = "https://files.pythonhosted.org/packages/16/4e/35e488548e952795829e129995c4174df33bf432b591d1aa42c8d9e4e7ad/coverage-7.15.2-cp314-cp314-win_arm64.whl", hash = "sha256:e38def96ad59853824c97953fdcd2c320a84ba3ce99b417db78af8bb6c3db635", size = 223760, upload-time = "2026-07-15T18:55:43.518Z" }, + { url = "https://files.pythonhosted.org/packages/ed/49/dd2c86cd6374038f6e415fb5bfb86db5218553209c081384a020369dee79/coverage-7.15.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:835ec4e20b45f0a7f63ed78f94065aca00de033403df8377bfe8b9c6abc0a7be", size = 222384, upload-time = "2026-07-15T18:55:45.569Z" }, + { url = "https://files.pythonhosted.org/packages/d3/74/173ff17a1c0808e5a438f549f6f145d5ac7528f2791310b63523e3200ac7/coverage-7.15.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7466cc7ab6dc0db871d264bf99e8779f0917ee63d40730af0552f71535a6e072", size = 222647, upload-time = "2026-07-15T18:55:47.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/f8/b8cba872162356fb44ac79c10309d987206a4461e32072fc29228dad7331/coverage-7.15.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e370c12133095ff18432de8c044962be85a5a96d90c6fcbce8e17e76236d2328", size = 264013, upload-time = "2026-07-15T18:55:49.768Z" }, + { url = "https://files.pythonhosted.org/packages/ee/67/a807a7586d0b8cae485308ddd55756f0806c92f8e0b411bacbf23c48edf3/coverage-7.15.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fe41909c9515c3bfdb5f02c4d1f857dba322d9a9a1178069b91eea77889df63a", size = 266135, upload-time = "2026-07-15T18:55:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ce/67/cd78771dc985f7e4ebdcc82b1a96d9a932af9e806f01f2f91a89f4c72e80/coverage-7.15.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6aa28cfb6488e5453b5b762d65f73aa586380f6693a04d58078ce228a29b06c0", size = 268555, upload-time = "2026-07-15T18:55:54.065Z" }, + { url = "https://files.pythonhosted.org/packages/18/3e/10134cf81275188c58568f324fc74aedff32c63ca4d5bbc513a91944a6f0/coverage-7.15.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bcc0aae933921d03096f53b0b03eeb702129fd406dee59f08d2efacc68681fa5", size = 269674, upload-time = "2026-07-15T18:55:56.066Z" }, + { url = "https://files.pythonhosted.org/packages/75/4a/771b77de446cba985dc414bbc5844bd21604da05dbc044286df8318a48a7/coverage-7.15.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7c63387e21ab21f512c69c9756a8c7dadd322c7275edb064064433c9a09c3743", size = 263101, upload-time = "2026-07-15T18:55:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/5f/b5/70a7011da15f4071943361183aefa27847f3e3aec4fd335f1cb3d3a622b1/coverage-7.15.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e55510bc98ae943cece9e667a6c0fe94c6a92913720dea34243657a17993d0c", size = 266007, upload-time = "2026-07-15T18:56:00.468Z" }, + { url = "https://files.pythonhosted.org/packages/b4/0d/f9547e804ce7ad49646ffeffac26699510efbe6c0f751b66fdc960c4e825/coverage-7.15.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2ff08701be2d1556fc78b326c80a3e8042da09352ecb3819105f8e386c8a3071", size = 263611, upload-time = "2026-07-15T18:56:02.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/59/f576a396659c0efd351f5c1544f67c3560e89c7761cabf7f65e412beeda5/coverage-7.15.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:38c9518b7103826c403a461544e3c2e77151e8676d06eaed85911a97e962584a", size = 267344, upload-time = "2026-07-15T18:56:04.622Z" }, + { url = "https://files.pythonhosted.org/packages/7c/5d/c2e4fce3579c0cb635024293f1a32bbe26df101b3e3a69f22243d1352b6c/coverage-7.15.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:dee88b1ed88587abd8c0269a1fc1f4cc77f7750d1dfde2869e2a123af420e67d", size = 262456, upload-time = "2026-07-15T18:56:06.641Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/956287d69436b66094bc4b57ac2da71e43bfd2a5524e958900b9f582fcf8/coverage-7.15.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2fbeeeecea279727f8ac16c8e1133ddfeee793e985c86ae343d6a5ce744eef8c", size = 264771, upload-time = "2026-07-15T18:56:08.795Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5a/6f979530c2734c575de77cf58f5f28d51f7123a94b5030fd9156fe5f363c/coverage-7.15.2-cp314-cp314t-win32.whl", hash = "sha256:cb0fddaa6884be6aae36ced9544b5e90f7d5f03845a2853bf47a14953a4e8688", size = 224151, upload-time = "2026-07-15T18:56:10.856Z" }, + { url = "https://files.pythonhosted.org/packages/54/7e/27f6b2a74d484742f4017553e710b01e396b23d809df3e95ca0bb9a2824b/coverage-7.15.2-cp314-cp314t-win_amd64.whl", hash = "sha256:77f091ea3a9cc611cd29f433565476bc1936c084ac8eee00ea0e7e70c27e4199", size = 224981, upload-time = "2026-07-15T18:56:12.928Z" }, + { url = "https://files.pythonhosted.org/packages/b1/48/284863423aa474240f6842bd00d680da22f4e6ea2e466618ef7c9c9e69a9/coverage-7.15.2-cp314-cp314t-win_arm64.whl", hash = "sha256:6fc448c377d6eeb00a47c673494bd9bae29280ca53987e1869e67ebedfe20658", size = 224294, upload-time = "2026-07-15T18:56:15.156Z" }, + { url = "https://files.pythonhosted.org/packages/ec/82/32e3bd191d498e64f6f911ad55d14006a0861e54869d2d32452326399e65/coverage-7.15.2-py3-none-any.whl", hash = "sha256:eb6bcae8d1a9d305351ecb108232441d11c5cfe9de840a04388ba5d2db8d735c", size = 213375, upload-time = "2026-07-15T18:56:17.305Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version <= '3.11'" }, +] + [[package]] name = "decorator" version = "5.3.1" @@ -459,6 +695,97 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "hypothesis" +version = "6.141.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "attrs" }, + { name = "exceptiongroup" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/20/8aa62b3e69fea68bb30d35d50be5395c98979013acd8152d64dc927e4cdb/hypothesis-6.141.1.tar.gz", hash = "sha256:8ef356e1e18fbeaa8015aab3c805303b7fe4b868e5b506e87ad83c0bf951f46f", size = 467389, upload-time = "2025-10-15T19:12:25.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/9a/f901858f139694dd669776983781b08a7c1717911025da6720e526bd8ce3/hypothesis-6.141.1-py3-none-any.whl", hash = "sha256:a5b3c39c16d98b7b4c3c5c8d4262e511e3b2255e6814ced8023af49087ad60b3", size = 535000, upload-time = "2025-10-15T19:12:21.659Z" }, +] + +[[package]] +name = "hypothesis" +version = "6.160.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.15'", + "python_full_version >= '3.11' and python_full_version < '3.15'", + "python_full_version == '3.10.*'", +] +dependencies = [ + { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/18/824aedbd4117d769862a2722ea2371aa61433a38bfb5355e5dc113b564c2/hypothesis-6.160.0.tar.gz", hash = "sha256:149400acbb7382e2ce6810a52e86a9fd6d4e5c4a47660818abb438cde76aa5d1", size = 485677, upload-time = "2026-07-22T14:12:13.331Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/c6/39fa718992b7529d1f68532a3554b9479f27f6a46aa5859c0d909bde0a40/hypothesis-6.160.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:69e1511325901fcd570fbd88779882e30cb280aeedd9708093aab4b25f7cdbf5", size = 766096, upload-time = "2026-07-22T14:11:58.283Z" }, + { url = "https://files.pythonhosted.org/packages/94/1b/81b54dbf97baa4026034579ce63b56d3d35c0d22b72b032c68e23bbda92b/hypothesis-6.160.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:1ba0f1dd0f2872b7f7230a3884a0d739917d57262d0e9e3c8ee34b775f95a553", size = 761752, upload-time = "2026-07-22T14:11:42.682Z" }, + { url = "https://files.pythonhosted.org/packages/ea/02/fa35cf37fd801d1e952e2168c0b5542f99c77024098f954cd515f2101910/hypothesis-6.160.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f9116a80ed96060a7fbc8d50cc5e93dec10d72f70f61e9184628dbcba2f9a2f", size = 1090928, upload-time = "2026-07-22T14:12:05.158Z" }, + { url = "https://files.pythonhosted.org/packages/a7/35/f2422a4287bbac99d6317a10e7add5f24abe069952c503cb3512e91bebc0/hypothesis-6.160.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:065cfed699889b6c05265ca4f97e8c7bb85800d3d3146f4741b68ef7be1fed18", size = 1140474, upload-time = "2026-07-22T14:11:50.558Z" }, + { url = "https://files.pythonhosted.org/packages/bc/ef/7504f31be0c9dfd8c69b1e068564e0c1126a82ab753abcb20c4bacd1544b/hypothesis-6.160.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:52e0cdc8fcd34b121a213205f239545fec38142014114afc721d1c867ac34834", size = 1132509, upload-time = "2026-07-22T14:11:48.702Z" }, + { url = "https://files.pythonhosted.org/packages/b4/39/8c7a5cfc336e0bdd7b7ae1d8807028b2b46c03979a5d82e8992b4ba2b81c/hypothesis-6.160.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4868821ffba805970441fec1b0635ea123f01aa6b71fc8f2d9550ee782f1ecd7", size = 1264762, upload-time = "2026-07-22T14:10:30.068Z" }, + { url = "https://files.pythonhosted.org/packages/ac/00/0d47e996ccbfa1eceb66d285b6fbf248c7c020e4e18b1bea09b18f05f6f5/hypothesis-6.160.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:64cf59670080aeb3c6048d62df0f6352586410745d14d7045a692eb5d2245110", size = 1307495, upload-time = "2026-07-22T14:11:33.978Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b8/01f731cfcf9fc475adbde3c328d0c8f1d24952b4dd2a5049e7156aa64d9c/hypothesis-6.160.0-cp310-abi3-win32.whl", hash = "sha256:993c26c81e9cc9f291cdb64f54aa8f31507d2d472d0f1334f8ba9e7d77666911", size = 651991, upload-time = "2026-07-22T14:11:21.375Z" }, + { url = "https://files.pythonhosted.org/packages/87/12/95216fe9a84cafc9bc721b4352cf9b78bf0e9089f278811fbd58c76dbe3f/hypothesis-6.160.0-cp310-abi3-win_amd64.whl", hash = "sha256:95a4b0e1faa366d0cc9d7ce261773cec69f4f130b845ca33b71c22c85493c35d", size = 658114, upload-time = "2026-07-22T14:10:54.298Z" }, + { url = "https://files.pythonhosted.org/packages/64/5c/24ef2013ded7f0980dcbbaf8bf96c21ca9f2c28ce8ca8885f664194023fc/hypothesis-6.160.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:47af99b07f3aab4c31559187c4d6ba2c34c7e0fcb907cbe6f30727545dfc578a", size = 766802, upload-time = "2026-07-22T14:11:04.045Z" }, + { url = "https://files.pythonhosted.org/packages/22/e9/9facbd7d836c6da1bf8babec6f6f7cdc212bf67d41b7996a286d1958185d/hypothesis-6.160.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7973d5956d1ea2fe936c8c99b64214ebb0d06c40ce99399c28bcecba05c54a02", size = 762568, upload-time = "2026-07-22T14:10:33.159Z" }, + { url = "https://files.pythonhosted.org/packages/61/06/f9b8a4accc4e7bb188a344b59d3eb9643047feefe9e79336959ff6527afe/hypothesis-6.160.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b1f415b89b6a1df913f2cda378f504775b4cd190fae5f3fefb08055fca8e3ab", size = 1091434, upload-time = "2026-07-22T14:12:09.055Z" }, + { url = "https://files.pythonhosted.org/packages/f2/37/142811f1fb096ae46a362e082aa5188f192884d6f0a797ec7acee359cc6b/hypothesis-6.160.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:796d295695229cb2f42ce454202b2512c5ed10843763b416a205b12c0a837711", size = 1140859, upload-time = "2026-07-22T14:10:41.687Z" }, + { url = "https://files.pythonhosted.org/packages/47/fc/49ec8afc6c75dca2c74fa72f1c875d6b89f37d2ae8e77481e30c2f7fb230/hypothesis-6.160.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6b95b19b8919629b433244801111c8e49d671b7769e8992aa11d4063206e4e8c", size = 1265411, upload-time = "2026-07-22T14:11:07.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/00/305b137542afa2bc86d428ac2fddb03f145b4c6aa5d77b83a86f38a536d6/hypothesis-6.160.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c6230309727b687ac5f37d7bc1dd61187196019ebc339a28a6374658c1c1363d", size = 1307668, upload-time = "2026-07-22T14:10:55.984Z" }, + { url = "https://files.pythonhosted.org/packages/d5/cc/7b1e469cd5bd5bc84cb3a45745cea0099aa1e880814e2ac07eaef741354a/hypothesis-6.160.0-cp310-cp310-win_amd64.whl", hash = "sha256:cee30c0a0b507b8bd4f196b18b8dc232ae3071617a148904c339c683b2eb008e", size = 658007, upload-time = "2026-07-22T14:10:36.027Z" }, + { url = "https://files.pythonhosted.org/packages/20/02/ea44b91cf1801a45152c65ee6bc159e9348329b7e40b784e88a65e3d0975/hypothesis-6.160.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4a93e8787cd74862cc1e3993046953ce0061b734d039681f108fad83dd5b7410", size = 766611, upload-time = "2026-07-22T14:11:30.619Z" }, + { url = "https://files.pythonhosted.org/packages/c7/97/d6b114fe4a7e62aea25447c0896ac45a2cc4e7efac125f9f0b8c6de2e351/hypothesis-6.160.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cc2b632673216b0a51796d60f7923e805f864c35aa6d0bef19399f0ea16c455", size = 762385, upload-time = "2026-07-22T14:10:49.75Z" }, + { url = "https://files.pythonhosted.org/packages/26/63/08ea3ce9ce9b03a41e3cb9e49ccd630d715c6a3117f27287d7cdd73dafeb/hypothesis-6.160.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfe893a54fe0a55778689b3cd56cb015e7895362db015a0f9450af9e6123f185", size = 1091280, upload-time = "2026-07-22T14:11:41.026Z" }, + { url = "https://files.pythonhosted.org/packages/2d/01/74f1bd57c9c29dfbe623f923500ccbc47684812ccb5cb961fbba225cad57/hypothesis-6.160.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31ee8c50c9d8e48023eab9769ea8498028c7c09e8582e2d2a5320c5ddab16dfa", size = 1140726, upload-time = "2026-07-22T14:11:32.249Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5b/472b9bc259ab4707c207ba74edaf28dc524b7d82911684c6a489277ec558/hypothesis-6.160.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c4a8f6187464ddb3603657fa99d17f81bb1be8859fd706c288175728dc1da7dd", size = 1265126, upload-time = "2026-07-22T14:10:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/a35460e97fd078987fa29b7080b09d5f613a879d5a6a901d54ae509e59db/hypothesis-6.160.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:109f9a549cbbcf05c273894c9b728d4e5be1d892145a10f338a3d308922a822d", size = 1307703, upload-time = "2026-07-22T14:12:00.226Z" }, + { url = "https://files.pythonhosted.org/packages/00/e8/646f041413f46b4d1f0996d5bdb9acf87a3acefbd3b99599d6ae22f291a5/hypothesis-6.160.0-cp311-cp311-win_amd64.whl", hash = "sha256:263327ab9eabd0ebe02d0dc1b21e4cf278f52abf5938ebfb21dbd43d75afb2cf", size = 657828, upload-time = "2026-07-22T14:10:44.904Z" }, + { url = "https://files.pythonhosted.org/packages/81/b2/bc800c4925c1f47b61c17f78e57bb58a8743d03da28de13f59cba148daf2/hypothesis-6.160.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:18e058b34f4514da8b2ce15ebee9e6e98d3a95067665accf394415824934f790", size = 767730, upload-time = "2026-07-22T14:11:56.44Z" }, + { url = "https://files.pythonhosted.org/packages/37/b6/d34a7f990eb0a38933a7f6b14d261fda990faef37122e71797b0043fa371/hypothesis-6.160.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b38697f797e9406e20e03cd79e1a69c7ac714e7e244f13121d39b44f27f7ed3", size = 759362, upload-time = "2026-07-22T14:11:05.77Z" }, + { url = "https://files.pythonhosted.org/packages/df/bf/48bd2bf246d22f188c82dbf3682832fc14fa4e6069c5415b1e8a473397a7/hypothesis-6.160.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d9e8022a8dd2afa2bfbf6580f21a7fd8b4798d20c027f4afb048d780414fd", size = 1089731, upload-time = "2026-07-22T14:11:39.069Z" }, + { url = "https://files.pythonhosted.org/packages/76/a0/d557bd44f611ec2516c69b6ada1e65f96c4d9d1dbad63f12b1799ca682b8/hypothesis-6.160.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4716ceb2adc72ea20138cd6a5600d102895f46fe95a42d915e032eed54b77ee6", size = 1139776, upload-time = "2026-07-22T14:11:19.164Z" }, + { url = "https://files.pythonhosted.org/packages/32/99/cad454acb11e027773bdba5cb95cb181a46cd1cabb8bfe2f2042e29dc0c5/hypothesis-6.160.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d186b17a25eaf51ebf0376ea9d702dddb4f62cc11c0b5230e0aae77b44f49d3", size = 1262564, upload-time = "2026-07-22T14:11:02.444Z" }, + { url = "https://files.pythonhosted.org/packages/67/e7/61b2e1b6c2f75fa3b791040ba4baf2b617ffaf62ffbafad9463869baf521/hypothesis-6.160.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e5e959bb18ec9b285dcc1d6f455c8860da919b9341842530847e820ed18dbbb", size = 1306756, upload-time = "2026-07-22T14:11:54.464Z" }, + { url = "https://files.pythonhosted.org/packages/89/79/6e9f2da0f298f891930a9fc1ed0559818d4ba840f47ed736c89152fd962e/hypothesis-6.160.0-cp312-cp312-win_amd64.whl", hash = "sha256:ded91bbdd0c3a84903bda3dc08d639b3b3e28c03fb83b568af8e13039042c3c4", size = 655265, upload-time = "2026-07-22T14:10:58.076Z" }, + { url = "https://files.pythonhosted.org/packages/85/05/a05ba058a37681d2aa872abcff9bd7a50c61c6347aedf2e3f5a15b8e932b/hypothesis-6.160.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:cb6cd703d38d881505a00e1901844d70d250e90824caa55e0dfaed6c8c7e0244", size = 767604, upload-time = "2026-07-22T14:11:11.346Z" }, + { url = "https://files.pythonhosted.org/packages/ec/a1/33dde1810a52698802fe2e28cfd2696b6aefafdc721cc456dfbc85875bb2/hypothesis-6.160.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9561298d687f9fca38aab451e8eb8a9f18b65a57f81f7331eff5234f0f065dc0", size = 759264, upload-time = "2026-07-22T14:10:40.271Z" }, + { url = "https://files.pythonhosted.org/packages/34/84/573402093577ef0fd86c8156d4c4ecd03b0a5e368e8925074fe565f9faba/hypothesis-6.160.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e19f91119e2e19603210b849508695efabd2a35d6af9ac4d637c1b9a514a52b", size = 1089653, upload-time = "2026-07-22T14:11:37.333Z" }, + { url = "https://files.pythonhosted.org/packages/da/05/c85a35fef75214fc08a27e5099ae51d713c6550252ef7ce4c156780433f1/hypothesis-6.160.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6b73076bb3fbf02001a439a5eb45cdd3db17e2cf6d95f453cfb1f5a97713f5", size = 1139592, upload-time = "2026-07-22T14:12:11.469Z" }, + { url = "https://files.pythonhosted.org/packages/59/53/8f9996fa3a6352edec2c17b743630b6c5f62486db6b43594168a1c0b7571/hypothesis-6.160.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c0dcde9c08f3bdd5318026c57155ce4bfe7615fd27d3eca77a7453cb3ffbba64", size = 1262616, upload-time = "2026-07-22T14:11:14.754Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f7/8b2699131893dd7bcecfe3be9ee758d3939cc8af68374700e68d9df2281b/hypothesis-6.160.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78cb5fcf8518f3a10e888cdff545fa733931e2ff843b02a54e5e0b01b3142f94", size = 1306470, upload-time = "2026-07-22T14:11:23.203Z" }, + { url = "https://files.pythonhosted.org/packages/88/ba/9764eaff70d2a54aa072f709a121f98cf8766fc1591a063f8fab2117b6cf/hypothesis-6.160.0-cp313-cp313-win_amd64.whl", hash = "sha256:e95c3ce8e9c5abd2256854a2e53395fdd91d16cdce8d1621eca8caf5c7a2b1a2", size = 655209, upload-time = "2026-07-22T14:11:17.33Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/3b92edf73785218f084521c2be9506ce6e5c63a64662cda074e588ff3071/hypothesis-6.160.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9bd3d333a501f1faf8611159a998eb1bb28c43b620822ba6c8b2463f5de2a136", size = 767796, upload-time = "2026-07-22T14:11:28.865Z" }, + { url = "https://files.pythonhosted.org/packages/12/c7/eefd510bffc66320015169e2c6669e3a08ea29dda84d81655ecc1c6cbd8c/hypothesis-6.160.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:21ee82802c25282d692eaec7d3b960176c10eb6dc70853b152c5bc6b3b6faf02", size = 759410, upload-time = "2026-07-22T14:10:31.902Z" }, + { url = "https://files.pythonhosted.org/packages/1d/e4/6ad1e558d2df6900b0ad9d17081fbed4a74ffb01d86e64813cab4eaf45f1/hypothesis-6.160.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7d71e85548be9dd3a6eb59904daa85d5879e337cb69ad42cc2267c05a17ab26", size = 1090131, upload-time = "2026-07-22T14:11:44.448Z" }, + { url = "https://files.pythonhosted.org/packages/69/94/0d2fef37f9ff89b38b943cc38e12b45fda47cd06704d09bdeb890063d3bc/hypothesis-6.160.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4af833bb623f37b185e53ad7c62292272fc9fec3c7567d0703e3fdd3dcc90945", size = 1139829, upload-time = "2026-07-22T14:12:02.462Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f3/216b8af797eda74af68b0d8ee37d8452adf0cf5b924dd25780e5c3b6296f/hypothesis-6.160.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5789a0cd225f216690d7d99159bbd5d01a6d42cb6c4a07233739b4bf59c7fa37", size = 1262992, upload-time = "2026-07-22T14:10:34.529Z" }, + { url = "https://files.pythonhosted.org/packages/d8/14/63f14de37f41ed09d56593d9c03e8389a3bffcdbdf71bf05d30b5e3b1e4f/hypothesis-6.160.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57f6e370e24c3ca4b9bb6cb132baa471745ca3d598f6328a602f590fe531b1e7", size = 1306760, upload-time = "2026-07-22T14:10:59.825Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/a94eb847dd98edf233aefb7dbe88bd7bf7506840896454ed03827f844907/hypothesis-6.160.0-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:5df6d4768d7a2d0bd82cd8704c2732cf80fd13089217a3b0ff7b330b59eb50c6", size = 599306, upload-time = "2026-07-22T14:11:09.704Z" }, + { url = "https://files.pythonhosted.org/packages/cb/10/01a5545d22d61320e5d9507a252cef37a138af97d5c17bcad8ea08bfa936/hypothesis-6.160.0-cp314-cp314-win_amd64.whl", hash = "sha256:bdafeab25029d1261786f68ce7aedaa5c0be3ad4accfb13b32ff206ef6dfaa40", size = 655149, upload-time = "2026-07-22T14:11:12.9Z" }, + { url = "https://files.pythonhosted.org/packages/91/d7/b170ae2dfeea3bc0edb99f361ccd725ce00120ddd2065590ed4281ffd29d/hypothesis-6.160.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:285f6763461d58ef1b9b75efd69b559ba3b91055c7c6fb34b1513b3666106a62", size = 766374, upload-time = "2026-07-22T14:10:37.579Z" }, + { url = "https://files.pythonhosted.org/packages/85/30/64e3ca8d5132688bed13bf0c35b4cb1061975f7bba9201c718c394b14fbb/hypothesis-6.160.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d7cefc720eaf6d80f4ee0be59a12e301f3d16a5941fdbefe11295ca7e567b0c2", size = 757876, upload-time = "2026-07-22T14:11:27.033Z" }, + { url = "https://files.pythonhosted.org/packages/25/a2/219da3305b412dc265be7ecdd846882ff4e399f84896ff561982bb9be0d3/hypothesis-6.160.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ec6ff81bace8494b12b6c2096e8fb18a769e861613a02138700a2cb5e4c1ccd", size = 1088723, upload-time = "2026-07-22T14:10:48.385Z" }, + { url = "https://files.pythonhosted.org/packages/8a/29/c1879c3a25f3069b1102d17bf2b6f6a7c0667128f1fb2efb2e9964bc17c1/hypothesis-6.160.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1c32bed39ecff19f68e37fef7ee4bcd1d13a82378fcd321b61d0cd2f1a360c8", size = 1138696, upload-time = "2026-07-22T14:10:52.712Z" }, + { url = "https://files.pythonhosted.org/packages/b6/15/16239bfc9aad85aa0a0166f61b8aa4eddc69ee57b0c68188f191f4ef0b00/hypothesis-6.160.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8d04e56812e135c3223cd06cd0016f61466ce7c56720167046d91123534240f5", size = 1261184, upload-time = "2026-07-22T14:10:43.241Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b8/aa6f06d42d1505b2dab0f82d133d84853391437f34a15c4c39cbcda04f6a/hypothesis-6.160.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c18c5eb6260bda6e56689429723d5b62b62cedee88c95de03976799645c9b0ce", size = 1305573, upload-time = "2026-07-22T14:10:51.193Z" }, + { url = "https://files.pythonhosted.org/packages/44/13/645f8c95070a21fa1257f0d4cf68b938d7ec60e8371d79402ce7cb50d3c9/hypothesis-6.160.0-cp314-cp314t-win_amd64.whl", hash = "sha256:deabcb5645076988ac52237a7c3ee8fca2fbd4f859461537374911fbe0e99817", size = 655308, upload-time = "2026-07-22T14:10:38.969Z" }, + { url = "https://files.pythonhosted.org/packages/48/0a/e9e6b8318fc5329b571e163b4391a3527073a010e133807c3452ade0befc/hypothesis-6.160.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e58f31d86abe8b92b2fca85ac4f744d7fdfd6dc0e9ca1df60e68cf351d3dabae", size = 767542, upload-time = "2026-07-22T14:11:46.615Z" }, + { url = "https://files.pythonhosted.org/packages/1a/b3/b736a8fe6d3e71d625c6e266a1311a756aaef29e9ed3bc65fe6e839b1ff0/hypothesis-6.160.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:96f3794173284a26440514ad1ea1e146e9971ce4b531ca350402454c34d2b595", size = 763447, upload-time = "2026-07-22T14:11:35.681Z" }, + { url = "https://files.pythonhosted.org/packages/09/d4/23344e0529cdfb65fb84b31a269d44a98d2e566885bef6ecf9b2d6e8870f/hypothesis-6.160.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:042a1a14d9c84162a36dfd9762f4c7050edf7853998ce2966ab88ac8dbe52fef", size = 1092274, upload-time = "2026-07-22T14:11:25.477Z" }, + { url = "https://files.pythonhosted.org/packages/6d/09/a05b13fdea13424e03ab720610be2b34acf3fd3d2f8e15313c964085f3be/hypothesis-6.160.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:876b05b3c23f13a0a10a4b2d3b693f612d490f79daa3ef859928317dfb6860c7", size = 1141985, upload-time = "2026-07-22T14:12:07.097Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0d/da98a11bb98853f2b55764000d275cc7e06c46292186a0ade735ca28f040/hypothesis-6.160.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:8beac87be57d8305fc46593086b60e1b4d0f5dba1544273ede3f09c926ba8225", size = 658946, upload-time = "2026-07-22T14:11:52.63Z" }, +] + [[package]] name = "identify" version = "2.6.15" @@ -1580,6 +1907,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" }, ] +[[package]] +name = "pytest-cov" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.10'" }, + { name = "coverage", version = "7.15.2", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.10'" }, + { name = "pluggy" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1802,6 +2145,8 @@ dependencies = [ dev = [ { name = "black", version = "25.11.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "black", version = "26.5.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "hypothesis", version = "6.141.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "hypothesis", version = "6.160.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "ipython", version = "8.39.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "ipython", version = "9.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, @@ -1811,6 +2156,7 @@ dev = [ { name = "pre-commit", version = "4.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "pytest", version = "9.1.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "pytest-cov" }, { name = "rich" }, { name = "ruff" }, { name = "types-xmltodict", version = "1.0.1.20260113", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, @@ -1830,10 +2176,12 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "black" }, + { name = "hypothesis", specifier = ">=6.141.1" }, { name = "ipython" }, { name = "mypy" }, { name = "pre-commit", specifier = ">=2.12.0" }, { name = "pytest", specifier = ">=7.4.0" }, + { name = "pytest-cov", specifier = ">=7.1.0" }, { name = "rich" }, { name = "ruff" }, { name = "types-xmltodict", specifier = ">=0.14.0.20241009" }, @@ -1874,6 +2222,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, +] + [[package]] name = "stack-data" version = "0.6.3" From ece30b5c79d9bcf198704f019489506494870116 Mon Sep 17 00:00:00 2001 From: dhvcc Date: Thu, 23 Jul 2026 01:26:27 +0200 Subject: [PATCH 18/20] docs: error contract, test architecture, migration notes --- docs/contributing.md | 45 ++++++++++++++++++++++++++++---------------- docs/migration.md | 10 ++++++++++ docs/parsing.md | 33 +++++++++++++++++++++++++++++--- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index 8ad782b..a66fa72 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -26,22 +26,35 @@ uv run ruff check . uv run mypy rss_parser ``` -## Sample-based snapshot tests - -Feed samples live in `tests/samples///` where `` is one of -`rss`, `atom`, `rdf`, `podcast`. Each sample dir contains: - -- `data.xml` — the feed -- `result.json` — the expected `model_dump` output - -Dropping a new sample directory in is enough — the snapshot test discovers it automatically. -To (re)generate `result.json` after adding a sample or intentionally changing model output: - -```bash -uv run python -m scripts.update_snapshots -``` - -Review the resulting diff carefully — the snapshots are the contract. +## Test architecture + +The suite is layered; each layer catches what the others can't: + +1. **Spec tests** (`test_spec_compliance.py`, `test_spec_atom.py`, `test_tag.py`, `test_dates.py`, + `test_detection.py`, `test_extensibility.py`, `test_itunes.py`, `test_errors.py`, + `test_serialization.py`) — targeted assertions with hand-written expected values. + This is where correctness lives. +2. **Property-based tests** (`test_properties.py`, [hypothesis](https://hypothesis.readthedocs.io)) — + invariants that must hold for *generated* feeds: text round-trips, lists are always lists, + unknown tags are never dropped, `model_validate(model_dump())` is idempotent. +3. **Real-world corpus** (`test_corpus.py`, `tests/corpus/`) — feeds captured from the wild + (BBC, NPR, YouTube, Slashdot, GitHub, podcasts...), parsed offline. Expectations in each + `expect.json` are derived by *reading the raw XML*, never by running the parser — + see `tests/corpus/README.md`. +4. **Serialization snapshots** (`test_snapshots.py`, `tests/samples/`) — full `model_dump` + contracts for small curated samples, guarding the output shape against accidental changes. + Regenerate after intentional changes with: + + ```bash + uv run python -m scripts.update_snapshots + ``` + + Review the resulting diff carefully — the snapshots are the contract. + +Coverage has a CI-enforced floor (`fail_under` in `pyproject.toml`). + +Both samples and corpus feeds are discovered automatically — dropping a new directory in +is enough. ## Docs diff --git a/docs/migration.md b/docs/migration.md index 071b004..e6e4f1a 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -31,6 +31,16 @@ the fields live directly on `Channel`, `Item`, `Feed`, `Entry`. Subclass those i **`atom.Entry.source`** is now a typed `Tag[Source]` instead of `Tag[str]`. +**Atom `updated` is optional.** The spec requires it, but major real-world publishers omit it +(YouTube feeds have no feed-level ``), so `Feed.updated` and `Entry.updated` can be `None`. + +**Malformed XML raises `InvalidXMLError`** (a `ValueError` subclass) instead of leaking +xmltodict's `ExpatError`. The original error is available as `__cause__`. + +**`Tag.flatten_tag_encoder` was removed.** It was the implementation detail behind +`json_plain()`/`dict_plain()`, which are now implemented properly (in 3.x they silently +returned the *unflattened* structure - this is also fixed). + **Unknown tags are kept.** Models now use pydantic's `extra="allow"`, so undeclared tags (e.g. `itunes:*` on the plain RSS schema) show up in `model_dump()` output and in `model_extra` instead of being dropped. diff --git a/docs/parsing.md b/docs/parsing.md index ab4b0e8..3eb8769 100644 --- a/docs/parsing.md +++ b/docs/parsing.md @@ -102,6 +102,31 @@ rdf.channel.content.model_extra["dc:publisher"] ## Strictness and errors +The full error contract: + +| Problem | Raised | +| --- | --- | +| Data is not well-formed XML | `InvalidXMLError` (the original `ExpatError` is `__cause__`) | +| Well-formed XML, but not a known feed root | `UnknownFeedTypeError` (only from `parse()`/`detect_feed_type()`) | +| Valid feed XML that violates the schema | pydantic `ValidationError` | + +Both rss-parser errors subclass `ValueError`, so `except ValueError` catches everything +except schema violations. + +```python +from rss_parser import parse, InvalidXMLError, UnknownFeedTypeError +from pydantic import ValidationError + +try: + feed = parse(data) +except InvalidXMLError: + ... # not XML +except UnknownFeedTypeError: + ... # XML, but not a feed +except ValidationError: + ... # a feed, but breaks the schema rules +``` + Validation errors are raised as pydantic `ValidationError` with a precise path to the problem: ``` @@ -110,6 +135,8 @@ channel.content.item.0.content Value error, either or <description> must be present in an <item> [type=value_error, ...] ``` -Required fields follow the specs: an RSS channel must have `title`, `link` and `description`; -an item must have at least one of `title`/`description`; an Atom feed/entry must have -`id`, `title` and `updated`. +Required fields follow the specs with one deliberate exception: an RSS channel must have +`title`, `link` and `description`; an item must have at least one of `title`/`description`; +an Atom feed/entry must have `id` and `title`. Atom's `updated` is required by the spec but +optional here, because major real-world publishers omit it (YouTube's feeds have no +feed-level `<updated>`). From ebea36d269eb5d2ae94d93ce6354274dbbaabdf9 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Fri, 24 Jul 2026 01:48:16 +0200 Subject: [PATCH 19/20] fix: recognize namespace-prefixed Atom feed roots A valid Atom document may bind the Atom namespace to a prefix (<atom:feed xmlns:atom="...">), in which case xmltodict keys the root as "atom:feed" and every Atom element carries the prefix. Both detect_feed_type() and the universal parse() raised UnknownFeedTypeError for such documents, and AtomParser could not validate them. Detect roots ending in ":feed" as Atom (mirroring the existing ":rdf" handling) and normalize the prefix away in AtomParser.parse_dict before validating the schema, leaving foreign-namespace elements untouched. --- rss_parser/_parser.py | 36 ++++++++++++++++++++++++++++++++- tests/test_detection.py | 24 +++++++++++++++++++++- tests/test_spec_atom.py | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 2 deletions(-) diff --git a/rss_parser/_parser.py b/rss_parser/_parser.py index 0f2071c..f06c678 100644 --- a/rss_parser/_parser.py +++ b/rss_parser/_parser.py @@ -92,11 +92,45 @@ class RSSParser(BaseParser): schema = RSS +def _strip_key_prefix(value: Any, prefix: str) -> Any: + """Recursively strip a namespace prefix (e.g. ``atom:``) from mapping keys.""" + if isinstance(value, Mapping): + return { + (key[len(prefix) :] if key.startswith(prefix) else key): _strip_key_prefix(item, prefix) + for key, item in value.items() + } + if isinstance(value, list): + return [_strip_key_prefix(item, prefix) for item in value] + return value + + class AtomParser(BaseParser): """Atom 1.0 parser.""" schema = Atom + @classmethod + def parse_dict( + cls, + root: Mapping[str, Any], + *, + schema: Optional[Type[XMLBaseModel]] = None, + root_key: Optional[str] = None, + ) -> XMLBaseModel: + if root_key is None: + # The Atom namespace may be bound to a prefix (e.g. <atom:feed>) - in that case + # every Atom element carries the prefix, so normalize it away to match the schema + feed_key = next((key for key in root if key.lower().endswith(":feed")), None) + if feed_key is not None: + prefix = feed_key[: -len("feed")] + root = { + ("feed" if key == feed_key else key): ( + _strip_key_prefix(value, prefix) if key == feed_key else value + ) + for key, value in root.items() + } + return super().parse_dict(root, schema=schema, root_key=root_key) + class RDFParser(BaseParser): """RSS 1.0 (RDF Site Summary) parser.""" @@ -139,7 +173,7 @@ def _detect_feed_type_from_root(root: Mapping[str, Any]) -> FeedType: lowered = key.lower() if lowered == "rss": return FeedType.RSS - if lowered == "feed": + if lowered == "feed" or lowered.endswith(":feed"): return FeedType.ATOM if lowered == "rdf" or lowered.endswith(":rdf"): return FeedType.RDF diff --git a/tests/test_detection.py b/tests/test_detection.py index a02310b..71de05d 100644 --- a/tests/test_detection.py +++ b/tests/test_detection.py @@ -12,6 +12,19 @@ ATOM = (SAMPLES_DIR / "atom" / "atom" / "data.xml").read_text(encoding="utf-8") RDF_1_0 = (SAMPLES_DIR / "rdf" / "rdf_1_0" / "data.xml").read_text(encoding="utf-8") +ATOM_NAMESPACED = """<?xml version="1.0" encoding="utf-8"?> +<atom:feed xmlns:atom="http://www.w3.org/2005/Atom"> + <atom:id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</atom:id> + <atom:title>Example Feed</atom:title> + <atom:updated>2003-12-13T18:30:02Z</atom:updated> + <atom:link href="http://example.org/"/> + <atom:entry> + <atom:id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</atom:id> + <atom:title>Atom-Powered Robots Run Amok</atom:title> + <atom:updated>2003-12-13T18:30:02Z</atom:updated> + </atom:entry> +</atom:feed>""" + class TestDetectFeedType: @pytest.mark.parametrize( @@ -20,9 +33,10 @@ class TestDetectFeedType: (RSS_2, FeedType.RSS), (RSS_0_91, FeedType.RSS), (ATOM, FeedType.ATOM), + (ATOM_NAMESPACED, FeedType.ATOM), (RDF_1_0, FeedType.RDF), ], - ids=["rss-2.0", "rss-0.91", "atom", "rdf-1.0"], + ids=["rss-2.0", "rss-0.91", "atom", "atom-namespaced", "rdf-1.0"], ) def test_detects_feed_type(self, data, expected): assert detect_feed_type(data) == expected @@ -50,6 +64,14 @@ def test_atom(self): assert isinstance(feed, Atom) + def test_atom_with_namespace_prefixed_root(self): + feed = parse(ATOM_NAMESPACED) + + assert isinstance(feed, Atom) + assert feed.feed.content.title.content == "Example Feed" + assert len(feed.feed.content.entries) == 1 + assert feed.feed.content.entries[0].content.title.content == "Atom-Powered Robots Run Amok" + def test_rdf(self): feed = parse(RDF_1_0) diff --git a/tests/test_spec_atom.py b/tests/test_spec_atom.py index 1b7128a..cddeee7 100644 --- a/tests/test_spec_atom.py +++ b/tests/test_spec_atom.py @@ -100,3 +100,48 @@ def test_entry_source_is_a_model(self): assert isinstance(source, Source) assert source.title.content == "Original feed" + + +class TestNamespacePrefixedRoot: + """A valid Atom document may bind the Atom namespace to a prefix instead of the default one.""" + + PREFIXED = """<?xml version="1.0" encoding="utf-8"?> +<atom:feed xmlns:atom="http://www.w3.org/2005/Atom"> + <atom:id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</atom:id> + <atom:title>Example Feed</atom:title> + <atom:updated>2003-12-13T18:30:02Z</atom:updated> + <atom:link href="http://example.org/"/> + <atom:entry> + <atom:id>urn:entry:1</atom:id> + <atom:title>Atom-Powered Robots Run Amok</atom:title> + </atom:entry> + <atom:entry> + <atom:id>urn:entry:2</atom:id> + <atom:title>Second Entry</atom:title> + </atom:entry> +</atom:feed>""" + + def test_prefixed_root_is_unwrapped_and_normalized(self): + feed = AtomParser.parse(self.PREFIXED).feed.content + + assert feed.id.content == "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6" + assert feed.title.content == "Example Feed" + assert isinstance(feed.updated.content, datetime) + assert feed.links[0].attributes["href"] == "http://example.org/" + + def test_prefixed_entries_are_normalized(self): + feed = AtomParser.parse(self.PREFIXED).feed.content + + assert len(feed.entries) == 2 + assert isinstance(feed.entries[0].content, Entry) + assert feed.entries[0].content.title.content == "Atom-Powered Robots Run Amok" + assert feed.entries[1].content.title.content == "Second Entry" + + def test_foreign_namespaces_keep_their_prefix(self): + data = self.PREFIXED.replace( + "<atom:entry>", + '<atom:entry xmlns:media="http://search.yahoo.com/mrss/"><media:thumbnail url="u"/>', + ) + feed = AtomParser.parse(data).feed.content + + assert feed.entries[0].content.model_extra["media:thumbnail"] == {"@url": "u"} From e62540d03c231fa70149974bd4a8767db387f5e2 Mon Sep 17 00:00:00 2001 From: dhvcc <alexey.artishevskiy@gmail.com> Date: Fri, 24 Jul 2026 01:48:30 +0200 Subject: [PATCH 20/20] fix: respect dump options in dict_plain/json_plain - dict_plain(by_alias=True) walked the dump using python field names while the dump is keyed by aliases (@version, pubDate, item), so every renamed field was silently dropped. Index the dump with the field's serialization key instead. - Tag flattening required the dump to have exactly {content, attributes} keys, but options like exclude_defaults drop default fields, leaving tags unflattened. Flatten any subset of the Tag shape. - json_plain() forwarded all kwargs to model_dump(), so JSON encoder options like indent=2 raised TypeError. Accept indent/separators/ sort_keys/ensure_ascii explicitly and pass them to json.dumps, sending the rest to dict_plain. - Tag.pre_convert now accepts partial Tag-shaped dicts (subset match, with a dict-typed attributes guard) so that model_validate(model_dump(exclude_defaults=True)) round-trips. --- rss_parser/models/__init__.py | 56 ++++++++++++++++++++------- rss_parser/models/types/tag.py | 6 ++- tests/test_serialization.py | 69 ++++++++++++++++++++++++++++++++++ tests/test_tag.py | 30 +++++++++++++++ 4 files changed, 146 insertions(+), 15 deletions(-) diff --git a/rss_parser/models/__init__.py b/rss_parser/models/__init__.py index 964cfba..76bf8d6 100644 --- a/rss_parser/models/__init__.py +++ b/rss_parser/models/__init__.py @@ -1,32 +1,45 @@ from __future__ import annotations import json -from typing import Any +from typing import Any, Optional, Tuple, Union from pydantic import BaseModel, ConfigDict from rss_parser.models.utils import camel_case +TAG_SHAPE_KEYS = frozenset(("content", "attributes")) -def _plainify(value: Any, dumped: Any) -> Any: + +def _dump_key(model: BaseModel, name: str, by_alias: bool) -> str: + """Return the key under which a field appears in ``model``'s dump.""" + if not by_alias: + return name + field = type(model).model_fields[name] + return field.serialization_alias or field.alias or name + + +def _plainify(value: Any, dumped: Any, by_alias: bool = False) -> Any: """Walk the model and its dump in parallel, flattening every Tag into its content.""" from rss_parser.models.types.tag import Tag # noqa: PLC0415 if isinstance(value, Tag): - if isinstance(dumped, dict) and set(dumped) == {"content", "attributes"}: - dumped = dumped["content"] - return _plainify(value.content, dumped) - if isinstance(value, BaseModel): + if isinstance(dumped, dict) and set(dumped) <= TAG_SHAPE_KEYS: + # Dump options such as exclude_defaults may drop "attributes" (or even "content"), + # so any subset of the Tag shape is still a Tag dump that must be flattened + dumped = dumped.get("content") + return _plainify(value.content, dumped, by_alias) + if isinstance(value, BaseModel) and isinstance(dumped, dict): result = {} for name in type(value).model_fields: - if name in dumped: # respects include/exclude kwargs - result[name] = _plainify(getattr(value, name), dumped[name]) + key = _dump_key(value, name, by_alias) + if key in dumped: # respects include/exclude kwargs + result[key] = _plainify(getattr(value, name), dumped[key], by_alias) for name in value.model_extra or {}: if name in dumped: result[name] = dumped[name] return result - if isinstance(value, (list, tuple)): - return [_plainify(item, dumped_item) for item, dumped_item in zip(value, dumped)] + if isinstance(value, (list, tuple)) and isinstance(dumped, (list, tuple)): + return [_plainify(item, dumped_item, by_alias) for item, dumped_item in zip(value, dumped)] return dumped @@ -52,13 +65,30 @@ def dict_plain(self, **kwargs) -> dict: Like ``model_dump(mode="json")``, but every Tag is flattened into its plain content value, dropping the content/attributes structure. """ - return _plainify(self, self.model_dump(mode="json", **kwargs)) + return _plainify(self, self.model_dump(mode="json", **kwargs), kwargs.get("by_alias", False)) - def json_plain(self, **kwargs) -> str: + def json_plain( + self, + *, + indent: Optional[Union[int, str]] = None, + separators: Optional[Tuple[str, str]] = None, + sort_keys: bool = False, + ensure_ascii: bool = False, + **kwargs, + ) -> str: """ Serialize the model to JSON while flattening Tag instances into their content. + + ``indent``, ``separators``, ``sort_keys`` and ``ensure_ascii`` are passed to + :func:`json.dumps`; all other kwargs are dump options passed to :meth:`dict_plain`. """ - return json.dumps(self.dict_plain(**kwargs), ensure_ascii=False) + return json.dumps( + self.dict_plain(**kwargs), + indent=indent, + separators=separators, + sort_keys=sort_keys, + ensure_ascii=ensure_ascii, + ) __all__ = ("XMLBaseModel",) diff --git a/rss_parser/models/types/tag.py b/rss_parser/models/types/tag.py index 39eb853..b689bfe 100644 --- a/rss_parser/models/types/tag.py +++ b/rss_parser/models/types/tag.py @@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, model_validator +from rss_parser.models import TAG_SHAPE_KEYS from rss_parser.models.utils import snake_case T = TypeVar("T") @@ -85,9 +86,10 @@ def pre_convert(cls, value: Union[T, dict, "Tag[T]"]) -> Union["Tag[T]", Dict[st return value if isinstance(value, dict): - if set(value) == {"content", "attributes"}: + if set(value) <= TAG_SHAPE_KEYS and isinstance(value.get("attributes", {}), dict): # Already in Tag shape (e.g. re-validating a model_dump) - keep as is, - # so that model_validate(model_dump()) round-trips + # so that model_validate(model_dump()) round-trips. Subset match, because + # dump options such as exclude_defaults may drop the default "attributes" return value data = deepcopy(value) diff --git a/tests/test_serialization.py b/tests/test_serialization.py index cebe994..a256172 100644 --- a/tests/test_serialization.py +++ b/tests/test_serialization.py @@ -57,3 +57,72 @@ def test_unknown_tags_survive_serialization(self): assert rss.model_dump()["channel"]["content"]["custom:tag"] == "kept" assert rss.dict_plain()["channel"]["custom:tag"] == "kept" + + def test_dict_plain_by_alias_uses_alias_keys(self): + plain = RSSParser.parse(XML).dict_plain(by_alias=True) + + assert plain["@version"] == "2.0" + item = plain["channel"]["item"][0] + assert item["title"] == "Item 1" + assert item["pubDate"] == "2002-09-07T00:00:01Z" + assert item["guid"] == "id-1" + + def test_json_plain_by_alias_matches_dict_plain(self): + rss = RSSParser.parse(XML) + + assert json.loads(rss.json_plain(by_alias=True)) == rss.dict_plain(by_alias=True) + + def test_dict_plain_exclude_defaults_still_flattens_tags(self): + plain = RSSParser.parse(XML).dict_plain(exclude_defaults=True) + + assert plain["version"] == "2.0" + assert plain["channel"]["title"] == "Title" + # guid keeps non-default attributes in the dump, but is still flattened to its content + assert plain["channel"]["items"][0]["guid"] == "id-1" + + +class TestJsonPlainEncoderOptions: + def test_indent_is_passed_to_the_json_encoder(self): + rss = RSSParser.parse(XML) + pretty = rss.json_plain(indent=2) + + assert '\n "version": "2.0"' in pretty + assert json.loads(pretty) == rss.dict_plain() + + def test_sort_keys_and_separators(self): + rss = RSSParser.parse(XML) + compact = rss.json_plain(sort_keys=True, separators=(",", ":")) + + assert '"version":"2.0"' in compact + assert json.loads(compact) == rss.dict_plain() + + def test_ensure_ascii(self): + rss = RSSParser.parse(XML.replace("<title>Title", "Тайтл")) + + assert '"Тайтл"' in rss.json_plain() + assert "\\u0422" in rss.json_plain(ensure_ascii=True) + + def test_encoder_options_combine_with_dump_options(self): + rss = RSSParser.parse(XML) + + assert json.loads(rss.json_plain(indent=2, by_alias=True)) == rss.dict_plain(by_alias=True) + + +class TestDumpRoundTrip: + def test_model_validate_model_dump_round_trips(self): + rss = RSSParser.parse(XML) + + assert type(rss).model_validate(rss.model_dump()) == rss + + def test_model_validate_exclude_defaults_dump_round_trips(self): + rss = RSSParser.parse(XML) + again = type(rss).model_validate(rss.model_dump(exclude_defaults=True)) + + assert again == rss + + def test_json_dump_round_trips(self): + rss = RSSParser.parse(XML) + again = type(rss).model_validate(json.loads(rss.model_dump_json(exclude_defaults=True))) + + assert again.channel.content.items[0].content.guid.content == "id-1" + assert again.channel.content.items[0].content.guid.attributes == {"is_perma_link": "false"} diff --git a/tests/test_tag.py b/tests/test_tag.py index 6dec37e..ffa0574 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -71,3 +71,33 @@ def test_getitem_forwards_to_content(self): m = make_model() assert m.category[:5] == "valid" + + +class TestDumpRoundTrip: + def test_full_dump_round_trips(self): + m = make_model() + + assert Model.model_validate(m.model_dump()) == m + + def test_exclude_defaults_dump_round_trips(self): + m = make_model() + again = Model.model_validate(m.model_dump(exclude_defaults=True)) + + assert again.width.content == 48 + assert again.width.attributes == {} + assert again.category.content == "valid string" + assert again.category.attributes == {"some_attribute": "https://example.com"} + + def test_content_only_dict_is_kept_as_tag_shape(self): + tag = Tag[int].model_validate({"content": 5}) + + assert tag.content == 5 + assert tag.attributes == {} + + def test_attributes_must_be_a_dict_to_count_as_tag_shape(self): + # An XML element literally named with a sibling text node + # is not a dumped Tag - it still goes through the attribute-splitting path + tag = Tag[dict].model_validate({"content": "x", "attributes": "y"}) + + assert tag.content == {"content": "x", "attributes": "y"} + assert tag.attributes == {}