From 142cc47f06019d0dddd551980d0c8ad4a2d15939 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Mar 2023 09:05:46 +0800 Subject: [PATCH] aioqzone H5 APIs support (#88) * feat!: support aioqzone h5 api * test: add login manager for heartbeat test * test: fix heartbeat mock object * test: simplify fixture using .h5() from aioqzone --------- Co-authored-by: JamzumSum --- doc/source/api/emoji.rst | 5 - doc/source/api/feed.rst | 3 + doc/source/event/index.rst | 2 +- .../locale/zh_CN/LC_MESSAGES/api/emoji.po | 101 ++--- .../locale/zh_CN/LC_MESSAGES/api/feed.po | 188 +++++--- .../locale/zh_CN/LC_MESSAGES/api/heartbeat.po | 61 ++- .../locale/zh_CN/LC_MESSAGES/event/index.po | 104 ++++- doc/source/locale/zh_CN/LC_MESSAGES/type.po | 281 +++++++++--- poetry.lock | 418 ++++++------------ pyproject.toml | 7 +- src/aioqzone_feed/api/emoji.py | 132 ------ src/aioqzone_feed/api/feed/__init__.py | 1 + src/aioqzone_feed/api/feed/h5.py | 170 +++++++ .../api/{feed.py => feed/web.py} | 48 +- src/aioqzone_feed/api/heartbeat.py | 37 +- src/aioqzone_feed/event/feed.py | 15 +- src/aioqzone_feed/type.py | 158 ++++++- test/api/conftest.py | 1 + test/api/test_emoji.py | 25 -- test/api/test_h5.py | 73 +++ test/api/test_heartbeat.py | 8 +- test/api/test_types.py | 8 + test/api/{test_feed.py => test_web.py} | 2 +- test/test_task.py | 4 + 24 files changed, 1163 insertions(+), 689 deletions(-) delete mode 100644 doc/source/api/emoji.rst delete mode 100644 src/aioqzone_feed/api/emoji.py create mode 100644 src/aioqzone_feed/api/feed/__init__.py create mode 100644 src/aioqzone_feed/api/feed/h5.py rename src/aioqzone_feed/api/{feed.py => feed/web.py} (89%) delete mode 100644 test/api/test_emoji.py create mode 100644 test/api/test_h5.py create mode 100644 test/api/test_types.py rename test/api/{test_feed.py => test_web.py} (96%) diff --git a/doc/source/api/emoji.rst b/doc/source/api/emoji.rst deleted file mode 100644 index 2e662b0..0000000 --- a/doc/source/api/emoji.rst +++ /dev/null @@ -1,5 +0,0 @@ -Emoji API -=============== - -.. automodule:: aioqzone_feed.api.emoji - :members: diff --git a/doc/source/api/feed.rst b/doc/source/api/feed.rst index d92ee00..f5fd8c7 100644 --- a/doc/source/api/feed.rst +++ b/doc/source/api/feed.rst @@ -3,3 +3,6 @@ Feed API .. autoclass:: aioqzone_feed.api.feed.FeedApi :members: + +.. autoclass:: aioqzone_feed.api.feed.h5.FeedH5Api + :members: diff --git a/doc/source/event/index.rst b/doc/source/event/index.rst index f91fe33..3c839f3 100644 --- a/doc/source/event/index.rst +++ b/doc/source/event/index.rst @@ -1,7 +1,7 @@ aioqzone-feed Events ========================= -aioqzone-feed follows the hook system defined in :external:mod:`qqqr.event`. +aioqzone-feed follows the hook system defined in :external:doc:`qqqr/event/index`. .. automodule:: aioqzone_feed.event.feed :members: diff --git a/doc/source/locale/zh_CN/LC_MESSAGES/api/emoji.po b/doc/source/locale/zh_CN/LC_MESSAGES/api/emoji.po index 4eda286..7482b6d 100644 --- a/doc/source/locale/zh_CN/LC_MESSAGES/api/emoji.po +++ b/doc/source/locale/zh_CN/LC_MESSAGES/api/emoji.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: aioqzone-feed \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-02-24 15:42+0800\n" +"POT-Creation-Date: 2023-03-08 21:56+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -17,81 +17,66 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.11.0\n" +"Generated-By: Babel 2.12.1\n" #: ../../source/api/emoji.rst:2 msgid "Emoji API" msgstr "" -#: aioqzone_feed.api.emoji:1 of -msgid "Translate emoji representation to text." -msgstr "" +#~ msgid "Translate emoji representation to text." +#~ msgstr "" -#: aioqzone_feed.api.emoji.query_wrap:1 of -msgid "" -"This function query the given eid and wraps it if it is not a pure emoji word. If the name is " -"not stored in the database, then it will return a ``[/em]`` tag." -msgstr "" +#~ msgid "" +#~ "This function query the given eid and wraps " +#~ "it if it is not a pure emoji word. If" +#~ " the name is not stored in the database, " +#~ "then it will return a ``[/em]`` tag." +#~ msgstr "" -#: aioqzone_feed.api.emoji.query_wrap aioqzone_feed.api.emoji.wrap_plain_text of -msgid "返回类型" -msgstr "" +#~ msgid "返回类型" +#~ msgstr "" -#: aioqzone_feed.api.emoji.query_wrap:4 aioqzone_feed.api.emoji.wrap_plain_text:7 of -msgid ":py:class:`str`" -msgstr "" +#~ msgid ":py:class:`str`" +#~ msgstr "" -#: aioqzone_feed.api.emoji.query_wrap aioqzone_feed.api.emoji.sub -#: aioqzone_feed.api.emoji.wrap_plain_text of -msgid "返回" -msgstr "" +#~ msgid "返回" +#~ msgstr "" -#: aioqzone_feed.api.emoji.query_wrap:5 of -msgid "a ``[/em]`` tag if query has no result, otherwise a wrapped name." -msgstr "" +#~ msgid "a ``[/em]`` tag if query has no result, otherwise a wrapped name." +#~ msgstr "" -#: aioqzone_feed.api.emoji.query_wrap:7 of -msgid ":meth:`wrap_plain_text`, :meth:`~qzemoji.utils.build_tag`" -msgstr "" +#~ msgid ":meth:`wrap_plain_text`, :meth:`~qzemoji.utils.build_tag`" +#~ msgstr "" -#: aioqzone_feed.api.emoji.sub:1 of -msgid "" -"The sub function is a async-version of :meth:`re.sub`. It works like pattern.sub(repl, text), " -"except for this `repl` is an async-function." -msgstr "" +#~ msgid "" +#~ "The sub function is a async-version of " +#~ ":meth:`re.sub`. It works like pattern.sub(repl, text), " +#~ "except for this `repl` is an async-function." +#~ msgstr "" -#: aioqzone_feed.api.emoji.sub aioqzone_feed.api.emoji.wrap_plain_text of -msgid "参数" -msgstr "" +#~ msgid "参数" +#~ msgstr "" -#: aioqzone_feed.api.emoji.sub:5 of -msgid "regex expression." -msgstr "" +#~ msgid "regex expression." +#~ msgstr "" -#: aioqzone_feed.api.emoji.sub:7 of -msgid "Used to replace the matched text. Input an :class:`re.Match`, returns a string, async." -msgstr "" +#~ msgid "Used to replace the matched text. Input an :class:`re.Match`, returns a string, async." +#~ msgstr "" -#: aioqzone_feed.api.emoji.sub:9 of -msgid "String to be searched." -msgstr "" +#~ msgid "String to be searched." +#~ msgstr "" -#: aioqzone_feed.api.emoji.sub:10 of -msgid "replaced string." -msgstr "" +#~ msgid "replaced string." +#~ msgstr "" -#: aioqzone_feed.api.emoji.wrap_plain_text:1 of -msgid "This function wraps the given `name` with the given `fmt`, if it is not a pure emoji word." -msgstr "" +#~ msgid "This function wraps the given `name` with the given `fmt`, if it is not a pure emoji word." +#~ msgstr "" -#: aioqzone_feed.api.emoji.wrap_plain_text:4 of -msgid "the customized emoji name." -msgstr "" +#~ msgid "the customized emoji name." +#~ msgstr "" -#: aioqzone_feed.api.emoji.wrap_plain_text:6 of -msgid "a format string in ``{`` style, default as ``[/{name}]``." -msgstr "" +#~ msgid "a format string in ``{`` style, default as ``[/{name}]``." +#~ msgstr "" -#: aioqzone_feed.api.emoji.wrap_plain_text:8 of -msgid "The emoji itself if it is a pure emoji word, otherwise a string wrapped by the `fmt`." -msgstr "" +#~ msgid "The emoji itself if it is a pure emoji word, otherwise a string wrapped by the `fmt`." +#~ msgstr "" diff --git a/doc/source/locale/zh_CN/LC_MESSAGES/api/feed.po b/doc/source/locale/zh_CN/LC_MESSAGES/api/feed.po index 4b46d23..e36a942 100644 --- a/doc/source/locale/zh_CN/LC_MESSAGES/api/feed.po +++ b/doc/source/locale/zh_CN/LC_MESSAGES/api/feed.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: aioqzone-feed \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-02-24 15:42+0800\n" +"POT-Creation-Date: 2023-03-09 14:18+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -17,139 +17,227 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.11.0\n" +"Generated-By: Babel 2.12.1\n" #: ../../source/api/feed.rst:2 msgid "Feed API" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule:1 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule:1 of msgid "" -"Drop feeds according to some rules. Dropping a feed will trigger :meth:`FeedEvent.FeedDropped` " -"event." +"Drop feeds according to some rules. No need to emit :meth:`FeedEvent.FeedDropped` event, it is " +"handled by :meth:`_dispatch_feed`." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule:4 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule:4 of msgid "Subclasses may inherit this method to customize their own rules." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule aioqzone_feed.api.feed.FeedApi.get_feeds_by_count -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second of msgid "参数" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule:7 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule:7 of msgid "the feed" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule aioqzone_feed.api.feed.FeedApi.get_feeds_by_count -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second aioqzone_feed.api.feed.FeedApi.new_batch -#: aioqzone_feed.api.feed.FeedApi.stop of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second +#: aioqzone_feed.api.feed.h5.FeedH5Api.new_batch aioqzone_feed.api.feed.h5.FeedH5Api.stop of msgid "返回类型" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule:8 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule:8 of msgid ":py:class:`bool`" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule aioqzone_feed.api.feed.FeedApi.get_feeds_by_count -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second aioqzone_feed.api.feed.FeedApi.new_batch of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second +#: aioqzone_feed.api.feed.h5.FeedH5Api.new_batch of msgid "返回" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.drop_rule:9 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.drop_rule:9 of msgid "if the feed is dropped." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:1 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:1 of msgid "Get feeds by count." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:4 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:4 of msgid "feeds count to get, max as 10, defaults to 10" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second of msgid "抛出" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:6 -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:9 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:6 +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:9 of msgid "qr login canceled" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:7 -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:10 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:7 +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:10 of msgid "not logined" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:8 -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:11 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:8 +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:11 of msgid "unexcpected error" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:10 -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:13 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:10 +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:13 of msgid ":py:class:`int`" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:11 -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:14 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:11 +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:14 of msgid "feeds num got actually." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:13 -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:16 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_count:13 +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:16 of msgid "..note:: You may need :meth:`.new_batch` to generate a new batch id." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_count:17 of -msgid "`FeedEvent.StopFeedFetch` works in this method as well." -msgstr "" - -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:1 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:1 of msgid "Get feeds by abstime (seconds). Range: `[start - second, start]`." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:4 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:4 of msgid "filter on abstime, calculate from `start`." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:6 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:6 of msgid "start timestamp, defaults to None, means now." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:7 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.get_feeds_by_second:7 of msgid "another criterion to judge if the feed is out of range, defaults to None" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.get_feeds_by_second:20 of -msgid "removed ``exceed_pred``, use `FeedEvent.StopFeedFetch` instead." -msgstr "" - -#: aioqzone_feed.api.feed.FeedApi.new_batch:1 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.new_batch:1 of msgid "The new_batch function edit internal batch id and return it." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.new_batch:3 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.new_batch:3 of msgid "" "A batch id can be used to identify a batch, thus even the same feed can have different id e.g. " "`(bid, uin, abstime)`." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.new_batch:5 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.new_batch:5 of msgid ":obj:`FeedEvent.TY_BID`" msgstr "" -#: aioqzone_feed.api.feed.FeedApi.new_batch:6 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.new_batch:6 of msgid "The batch_id." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.stop:1 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.stop:1 of msgid "Clear **all** registered tasks. All tasks will be CANCELLED if not finished." msgstr "" -#: aioqzone_feed.api.feed.FeedApi.stop:4 of +#: aioqzone_feed.api.feed.h5.FeedH5Api.stop:4 of msgid ":py:obj:`None`" msgstr "" + +#~ msgid "" +#~ "Drop feeds according to some rules. Dropping a " +#~ "feed will trigger :meth:`FeedEvent.FeedDropped` event." +#~ msgstr "" + +#~ msgid "Subclasses may inherit this method to customize their own rules." +#~ msgstr "" + +#~ msgid "参数" +#~ msgstr "" + +#~ msgid "the feed" +#~ msgstr "" + +#~ msgid "返回类型" +#~ msgstr "" + +#~ msgid ":py:class:`bool`" +#~ msgstr "" + +#~ msgid "返回" +#~ msgstr "" + +#~ msgid "if the feed is dropped." +#~ msgstr "" + +#~ msgid "Get feeds by count." +#~ msgstr "" + +#~ msgid "feeds count to get, max as 10, defaults to 10" +#~ msgstr "" + +#~ msgid "抛出" +#~ msgstr "" + +#~ msgid "qr login canceled" +#~ msgstr "" + +#~ msgid "not logined" +#~ msgstr "" + +#~ msgid "unexcpected error" +#~ msgstr "" + +#~ msgid ":py:class:`int`" +#~ msgstr "" + +#~ msgid "feeds num got actually." +#~ msgstr "" + +#~ msgid "..note:: You may need :meth:`.new_batch` to generate a new batch id." +#~ msgstr "" + +#~ msgid "`FeedEvent.StopFeedFetch` works in this method as well." +#~ msgstr "" + +#~ msgid "Get feeds by abstime (seconds). Range: `[start - second, start]`." +#~ msgstr "" + +#~ msgid "filter on abstime, calculate from `start`." +#~ msgstr "" + +#~ msgid "start timestamp, defaults to None, means now." +#~ msgstr "" + +#~ msgid "another criterion to judge if the feed is out of range, defaults to None" +#~ msgstr "" + +#~ msgid "removed ``exceed_pred``, use `FeedEvent.StopFeedFetch` instead." +#~ msgstr "" + +#~ msgid "The new_batch function edit internal batch id and return it." +#~ msgstr "" + +#~ msgid "" +#~ "A batch id can be used to identify a " +#~ "batch, thus even the same feed can have " +#~ "different id e.g. `(bid, uin, abstime)`." +#~ msgstr "" + +#~ msgid ":obj:`FeedEvent.TY_BID`" +#~ msgstr "" + +#~ msgid "The batch_id." +#~ msgstr "" + +#~ msgid "Clear **all** registered tasks. All tasks will be CANCELLED if not finished." +#~ msgstr "" + +#~ msgid ":py:obj:`None`" +#~ msgstr "" diff --git a/doc/source/locale/zh_CN/LC_MESSAGES/api/heartbeat.po b/doc/source/locale/zh_CN/LC_MESSAGES/api/heartbeat.po index 2c79114..680fddf 100644 --- a/doc/source/locale/zh_CN/LC_MESSAGES/api/heartbeat.po +++ b/doc/source/locale/zh_CN/LC_MESSAGES/api/heartbeat.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: aioqzone-feed \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-02-24 15:43+0800\n" +"POT-Creation-Date: 2023-03-09 14:12+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -17,7 +17,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.11.0\n" +"Generated-By: Babel 2.12.1\n" #: ../../source/api/heartbeat.rst:2 msgid "Heartbeat API" @@ -100,3 +100,60 @@ msgstr "" #~ ":external:meth:`aioqzone.api.DummyQapi.get_feeds_count` and handles all " #~ "kinds of excpetions raised during heartbeat." #~ msgstr "" + +#~ msgid "" +#~ "A helper function that creates a heartbeat task " +#~ "and keep a ref of it. A heartbeat task " +#~ "is a timer that circularly calls `.heartbeat_refresh`." +#~ msgstr "" + +#~ msgid "参数" +#~ msgstr "" + +#~ msgid "max retry times when some exceptions occurs, defaults to 5." +#~ msgstr "" + +#~ msgid "retry interval, defaults to 5." +#~ msgstr "" + +#~ msgid "heartbeat interval, defaults to 300." +#~ msgstr "" + +#~ msgid "timer name" +#~ msgstr "" + +#~ msgid "返回" +#~ msgstr "" + +#~ msgid "the heartbeat task" +#~ msgstr "" + +#~ msgid "" +#~ "A wrapper function that calls " +#~ ":external:meth:`aioqzone.api.QzoneWebAPI.get_feeds_count` and handles all" +#~ " kinds of excpetions raised during heartbeat." +#~ msgstr "" + +#~ msgid "" +#~ "This method calls heartbeat **ONLY ONCE** so it " +#~ "should be called circularly by using `.add_heartbeat` " +#~ "or other timer/scheduler." +#~ msgstr "" + +#~ msgid "retry times on QzoneError, default as 2." +#~ msgstr "" + +#~ msgid "retry interval on QzoneError" +#~ msgstr "" + +#~ msgid "whether the timer should stop, means heartbeat will always fail until something is changed." +#~ msgstr "" + +#~ msgid "Clear **all** registered tasks. All tasks will be CANCELLED if not finished." +#~ msgstr "" + +#~ msgid "返回类型" +#~ msgstr "" + +#~ msgid ":py:obj:`None`" +#~ msgstr "" diff --git a/doc/source/locale/zh_CN/LC_MESSAGES/event/index.po b/doc/source/locale/zh_CN/LC_MESSAGES/event/index.po index ce4de0b..4753f6d 100644 --- a/doc/source/locale/zh_CN/LC_MESSAGES/event/index.po +++ b/doc/source/locale/zh_CN/LC_MESSAGES/event/index.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: aioqzone-feed \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-02-24 15:43+0800\n" +"POT-Creation-Date: 2023-03-09 14:23+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language: zh_CN\n" @@ -17,14 +17,14 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.11.0\n" +"Generated-By: Babel 2.12.1\n" #: ../../source/event/index.rst:2 msgid "aioqzone-feed Events" msgstr "" #: ../../source/event/index.rst:4 -msgid "aioqzone-feed follows the hook system defined in :external:mod:`qqqr.event`." +msgid "aioqzone-feed follows the hook system defined in :external:doc:`qqqr/event/index`." msgstr "" #: aioqzone_feed.event.feed.FeedEvent.FeedDropped:1 of @@ -48,6 +48,10 @@ msgstr "" msgid "Used to pass a ref to the feed." msgstr "" +#: aioqzone_feed.event.feed.FeedEvent.FeedDropped:10 of +msgid "`feed` is a :class:`BaseFeed` type." +msgstr "" + #: aioqzone_feed.event.feed.FeedEvent.FeedMediaUpdate:1 of msgid "" "The FeedMediaUpdate function is used to update the media of a feed. The " @@ -84,6 +88,10 @@ msgid "" "will also be used in `FeedApi.get_feeds_by_count`. :rtype: :py:class:`bool`" msgstr "" +#: aioqzone_feed.event.feed.FeedEvent.StopFeedFetch:13 of +msgid "`feed` might be a :class:`FeedData` for web/h5 api compatability." +msgstr "" + #: aioqzone_feed.event.heartbeat.HeartbeatEvent.HeartbeatFailed:1 of msgid "" "The HeartbeatFailed function is called when the heartbeat fails. It can be used to log an error " @@ -117,3 +125,93 @@ msgstr "" #~ "the slow api. The media will be update by " #~ "raw photos/videos, list order should not be changed" #~ msgstr "" + +#~ msgid "" +#~ "The FeedDropped hook is called when a feed is" +#~ " dropped for hitting some rules (e.g. advertisement)" +#~ msgstr "" + +#~ msgid "参数" +#~ msgstr "" + +#~ msgid "Used to identify feed batch (tell from different calling)." +#~ msgstr "" + +#~ msgid "Used to pass a ref to the feed." +#~ msgstr "" + +#~ msgid "" +#~ "The FeedMediaUpdate function is used to update the" +#~ " media of a feed. The " +#~ ":external:meth:`aioqzone.api.QzoneWebAPI.floatview_photo_list` is one of" +#~ " the slow api. The media will be update by" +#~ " raw photos/videos, list order should not be " +#~ "changed" +#~ msgstr "" + +#~ msgid "" +#~ "To tell which feed is updated. The feed " +#~ "itself is updated by ref already, the ref " +#~ "should have been passed by :meth:`.FeedProcEnd` in " +#~ "the past." +#~ msgstr "" + +#~ msgid "" +#~ "The FeedProcEnd function is called when all " +#~ "processes must be done have finished (i.e. except" +#~ " for slow-api that cannot return at once, " +#~ "and may not matters a lot)" +#~ msgstr "" + +#~ msgid "Used to pass the feed content." +#~ msgstr "" + +#~ msgid "" +#~ "Used to judge if a feed fetching loop should" +#~ " break. Once this hook returns `True`, new pages" +#~ " will not be fetched any more. Note that " +#~ "the rest feeds of current page may still " +#~ "trigger `FeedProcEnd`." +#~ msgstr "" + +#~ msgid "" +#~ "This is used to replace the ``exceed_pred`` " +#~ "paramater of `FeedApi.get_feeds_by_second`. This will also " +#~ "be used in `FeedApi.get_feeds_by_count`. :rtype: " +#~ ":py:class:`bool`" +#~ msgstr "" + +#~ msgid "" +#~ "The HeartbeatFailed function is called when the " +#~ "heartbeat fails. It can be used to log an " +#~ "error and call :meth:`aioqzone_feed.api.feed.FeedApi.add_heartbeat` " +#~ "again if possible." +#~ msgstr "" + +#~ msgid "" +#~ "Used to pass an exception object that can be" +#~ " used to determine the cause of the heartbeat" +#~ " failure." +#~ msgstr "" + +#~ msgid "" +#~ "This event is triggered after a heartbeat succeeded" +#~ " and there are new feeds. Use this event " +#~ "to wait for all dispatch task to be finished," +#~ " and send received feeds." +#~ msgstr "" + +#~ msgid "number of new feeds" +#~ msgstr "" + +#~ msgid "Example:" +#~ msgstr "" + +#~ msgid "aioqzone-feed follows the hook system defined in :external:mod:`qqqr.event`." +#~ msgstr "" + +#~ msgid "aioqzone-feed follows the hook system defined in :external:`qqqr.event`." +#~ msgstr "" + +#~ msgid "aioqzone-feed follows the hook system defined in :external:doc:`qqqr.event`." +#~ msgstr "" diff --git a/doc/source/locale/zh_CN/LC_MESSAGES/type.po b/doc/source/locale/zh_CN/LC_MESSAGES/type.po index 203fd24..cb98292 100644 --- a/doc/source/locale/zh_CN/LC_MESSAGES/type.po +++ b/doc/source/locale/zh_CN/LC_MESSAGES/type.po @@ -9,54 +9,64 @@ msgid "" msgstr "" "Project-Id-Version: aioqzone-feed \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-10-18 13:15+0800\n" +"POT-Creation-Date: 2023-03-09 14:12+0800\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.10.3\n" +"Generated-By: Babel 2.12.1\n" #: ../../source/type.rst:2 msgid "aioqzone-feed Types" msgstr "" -#: aioqzone_feed.type.BaseDetail aioqzone_feed.type.BaseFeed -#: aioqzone_feed.type.FeedContent aioqzone_feed.type.VisualMedia of +#: aioqzone_feed.type.BaseDetail aioqzone_feed.type.BaseFeed aioqzone_feed.type.FeedContent +#: aioqzone_feed.type.VisualMedia of +msgid "Config" +msgstr "" + +#: aioqzone_feed.type.BaseDetail:1 aioqzone_feed.type.BaseFeed:1 aioqzone_feed.type.FeedContent:1 +#: aioqzone_feed.type.VisualMedia:1 of +msgid "**keep_untouched**: *tuple = (,)*" +msgstr "" + +#: aioqzone_feed.type.BaseDetail aioqzone_feed.type.BaseFeed aioqzone_feed.type.FeedContent +#: aioqzone_feed.type.VisualMedia of msgid "Fields" msgstr "" #: aioqzone_feed.type.BaseDetail:1 of msgid "" -":py:obj:`entities (Optional[List[aioqzone.type.entity.ConEntity]]) " +":py:obj:`entities (List[aioqzone.type.entity.ConEntity]) " "`" msgstr "" #: aioqzone_feed.type.BaseDetail:1 of msgid "" -":py:obj:`forward (Optional[Union[pydantic.networks.HttpUrl, str, " -"aioqzone_feed.type.BaseFeed]]) `" +":py:obj:`forward (pydantic.networks.HttpUrl | str | aioqzone_feed.type.BaseFeed | None) " +"`" msgstr "" #: aioqzone_feed.type.BaseDetail:1 of -msgid "" -":py:obj:`media (Optional[List[aioqzone_feed.type.VisualMedia]]) " -"`" +msgid ":py:obj:`media (List[aioqzone_feed.type.VisualMedia]) `" msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseDetail.forward:1 of +#: ../../docstring aioqzone_feed.type.BaseDetail.forward:1 of msgid "unikey to the feed, or the content itself." msgstr "" -#: aioqzone_feed.type.BaseFeed:1 of -msgid "" -"FeedModel is a model for storing a feed, with the info to hashing and " -"retrieving the feed." +#: aioqzone_feed.type.BaseDetail.set_detail of +msgid "返回类型" msgstr "" -#: aioqzone_feed.type.BaseFeed aioqzone_feed.type.FeedContent of -msgid "Config" +#: aioqzone_feed.type.BaseDetail.set_detail:2 of +msgid ":py:obj:`None`" +msgstr "" + +#: aioqzone_feed.type.BaseFeed:1 of +msgid "FeedModel is a model for storing a feed, with the info to hashing and retrieving the feed." msgstr "" #: aioqzone_feed.type.BaseFeed:1 aioqzone_feed.type.FeedContent:1 of @@ -72,9 +82,7 @@ msgid ":py:obj:`appid (int) `" msgstr "" #: aioqzone_feed.type.BaseFeed:1 of -msgid "" -":py:obj:`curkey (Optional[Union[pydantic.networks.HttpUrl, str]]) " -"`" +msgid ":py:obj:`curkey (pydantic.networks.HttpUrl | str | None) `" msgstr "" #: aioqzone_feed.type.BaseFeed:1 of @@ -98,55 +106,91 @@ msgid ":py:obj:`uin (int) `" msgstr "" #: aioqzone_feed.type.BaseFeed:1 of -msgid "" -":py:obj:`unikey (Optional[Union[pydantic.networks.HttpUrl, str]]) " -"`" +msgid ":py:obj:`unikey (pydantic.networks.HttpUrl | str | None) `" msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.abstime:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.abstime:1 of msgid "Feed created time. common alias: `created_time`" msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.curkey:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.curkey:1 of msgid "The identifier to this feed. May be a url, or just a identifier string." msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.fid:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.fid:1 of msgid "Feed id, a hex string with 24/32 chars, or a much shorter placeholder." msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.fid:4 of +#: ../../docstring aioqzone_feed.type.BaseFeed.fid:4 of msgid "" -"fid is not a enough identifier for ANY feed. For comman feed that " -"appid==311, it is a 24 or 32 length hex string, which might be satisfied." -" But for shares that appid!=311, it is a short string and is commonly " -"used by multiple shares. So do not distinguish all feeds on this field." +"fid is not a enough identifier for ANY feed. For comman feed that appid==311, it is a 24 or 32 " +"length hex string, which might be satisfied. But for shares that appid!=311, it is a short " +"string and is commonly used by multiple shares. So do not distinguish all feeds on this field." msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.nickname:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.nickname:1 of msgid "Feed owner nickname." msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.topicId:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.topicId:1 of msgid "" -"This is used to reply to this feed, or can be used to update this feed if" -" current user own this feed." +"This is used to reply to this feed, or can be used to update this feed if current user own this " +"feed." msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.uin:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.uin:1 of msgid "Feed owner uin. (hostuin)" msgstr "" -#: ../../../docstring aioqzone_feed.type.BaseFeed.unikey:1 of +#: ../../docstring aioqzone_feed.type.BaseFeed.unikey:1 of +msgid "" +"The identifier to the original content. May be a url in all kinds (sometimes not strictly in a " +"correct format, but it is from the meaning)" +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:1 of +msgid "classmethod(function) -> method" +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:3 of +msgid "Convert a function to be a class method." +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:5 of +msgid "" +"A class method receives the class as implicit first argument, just like an instance method " +"receives the instance. To declare a class method, use this idiom:" +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:12 of +msgid "class C:" +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:10 of +msgid "@classmethod def f(cls, arg1, arg2, ...):" +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:12 of +msgid "..." +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:14 of msgid "" -"The identifier to the original content. May be a url in all kinds " -"(sometimes not strictly in a correct format, but it is from the meaning)" +"It can be called either on the class (e.g. C.f()) or on an instance (e.g. C().f()). The " +"instance is ignored except for its class. If a class method is called for a derived class, the " +"derived class object is passed as the implied first argument." +msgstr "" + +#: functools.singledispatchmethod.__get__.._method:19 of +msgid "" +"Class methods are different than C++ or Java static methods. If you want those, see the " +"staticmethod builtin." msgstr "" #: aioqzone_feed.type.FeedContent:1 of msgid "" -"FeedContent is feed with contents. This might be the common structure to " -"represent a feed as what it's known." +"FeedContent is feed with contents. This might be the common structure to represent a feed as " +"what it's known." msgstr "" #: aioqzone_feed.type.FeedContent:1 of @@ -162,37 +206,164 @@ msgid ":py:obj:`is_video (bool) `" msgstr "" #: aioqzone_feed.type.VisualMedia:1 of -msgid "" -":py:obj:`raw (pydantic.networks.HttpUrl) " -"`" +msgid ":py:obj:`raw (pydantic.networks.HttpUrl) `" msgstr "" #: aioqzone_feed.type.VisualMedia:1 of -msgid "" -":py:obj:`thumbnail (pydantic.networks.HttpUrl) " -"`" +msgid ":py:obj:`thumbnail (pydantic.networks.HttpUrl | None) `" msgstr "" #: aioqzone_feed.type.VisualMedia:1 of msgid ":py:obj:`width (int) `" msgstr "" -#: ../../../docstring aioqzone_feed.type.VisualMedia.raw -#: aioqzone_feed.type.VisualMedia.thumbnail of +#: ../../docstring aioqzone_feed.type.VisualMedia.raw aioqzone_feed.type.VisualMedia.thumbnail of msgid "Constraints" msgstr "" -#: ../../../docstring aioqzone_feed.type.VisualMedia.raw:1 -#: aioqzone_feed.type.VisualMedia.thumbnail:1 of +#: ../../docstring aioqzone_feed.type.VisualMedia.raw:1 aioqzone_feed.type.VisualMedia.thumbnail:1 of msgid "**minLength** = 1" msgstr "" -#: ../../../docstring aioqzone_feed.type.VisualMedia.raw:1 -#: aioqzone_feed.type.VisualMedia.thumbnail:1 of +#: ../../docstring aioqzone_feed.type.VisualMedia.raw:1 aioqzone_feed.type.VisualMedia.thumbnail:1 of msgid "**maxLength** = 2083" msgstr "" -#: ../../../docstring aioqzone_feed.type.VisualMedia.raw:1 -#: aioqzone_feed.type.VisualMedia.thumbnail:1 of +#: ../../docstring aioqzone_feed.type.VisualMedia.raw:1 aioqzone_feed.type.VisualMedia.thumbnail:1 of msgid "**format** = uri" msgstr "" + +#~ msgid "Fields" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`entities (Optional[List[aioqzone.type.entity.ConEntity]]) " +#~ "`" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`forward (Optional[Union[pydantic.networks.HttpUrl, str, " +#~ "aioqzone_feed.type.BaseFeed]]) `" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`media (Optional[List[aioqzone_feed.type.VisualMedia]]) " +#~ "`" +#~ msgstr "" + +#~ msgid "unikey to the feed, or the content itself." +#~ msgstr "" + +#~ msgid "FeedModel is a model for storing a feed, with the info to hashing and retrieving the feed." +#~ msgstr "" + +#~ msgid "Config" +#~ msgstr "" + +#~ msgid "**orm_mode**: *bool = True*" +#~ msgstr "" + +#~ msgid ":py:obj:`abstime (int) `" +#~ msgstr "" + +#~ msgid ":py:obj:`appid (int) `" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`curkey (Optional[Union[pydantic.networks.HttpUrl, str]]) " +#~ "`" +#~ msgstr "" + +#~ msgid ":py:obj:`fid (str) `" +#~ msgstr "" + +#~ msgid ":py:obj:`nickname (str) `" +#~ msgstr "" + +#~ msgid ":py:obj:`topicId (str) `" +#~ msgstr "" + +#~ msgid ":py:obj:`typeid (int) `" +#~ msgstr "" + +#~ msgid ":py:obj:`uin (int) `" +#~ msgstr "" + +#~ msgid "" +#~ ":py:obj:`unikey (Optional[Union[pydantic.networks.HttpUrl, str]]) " +#~ "`" +#~ msgstr "" + +#~ msgid "Feed created time. common alias: `created_time`" +#~ msgstr "" + +#~ msgid "The identifier to this feed. May be a url, or just a identifier string." +#~ msgstr "" + +#~ msgid "Feed id, a hex string with 24/32 chars, or a much shorter placeholder." +#~ msgstr "" + +#~ msgid "" +#~ "fid is not a enough identifier for ANY feed." +#~ " For comman feed that appid==311, it is a " +#~ "24 or 32 length hex string, which might be" +#~ " satisfied. But for shares that appid!=311, it " +#~ "is a short string and is commonly used by " +#~ "multiple shares. So do not distinguish all feeds" +#~ " on this field." +#~ msgstr "" + +#~ msgid "Feed owner nickname." +#~ msgstr "" + +#~ msgid "" +#~ "This is used to reply to this feed, or " +#~ "can be used to update this feed if current" +#~ " user own this feed." +#~ msgstr "" + +#~ msgid "Feed owner uin. (hostuin)" +#~ msgstr "" + +#~ msgid "" +#~ "The identifier to the original content. May be " +#~ "a url in all kinds (sometimes not strictly in" +#~ " a correct format, but it is from the " +#~ "meaning)" +#~ msgstr "" + +#~ msgid "" +#~ "FeedContent is feed with contents. This might be" +#~ " the common structure to represent a feed as " +#~ "what it's known." +#~ msgstr "" + +#~ msgid ":py:obj:`islike (int) `" +#~ msgstr "" + +#~ msgid ":py:obj:`height (int) `" +#~ msgstr "" + +#~ msgid ":py:obj:`is_video (bool) `" +#~ msgstr "" + +#~ msgid ":py:obj:`raw (pydantic.networks.HttpUrl) `" +#~ msgstr "" + +#~ msgid ":py:obj:`thumbnail (pydantic.networks.HttpUrl) `" +#~ msgstr "" + +#~ msgid ":py:obj:`width (int) `" +#~ msgstr "" + +#~ msgid "Constraints" +#~ msgstr "" + +#~ msgid "**minLength** = 1" +#~ msgstr "" + +#~ msgid "**maxLength** = 2083" +#~ msgstr "" + +#~ msgid "**format** = uri" +#~ msgstr "" diff --git a/poetry.lock b/poetry.lock index e34ca35..2d4d6bf 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,15 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "aioqzone" -version = "0.12.5.dev2" +version = "0.12.7.dev1" description = "Python wrapper for Qzone web login and Qzone http api." category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ - {file = "aioqzone-0.12.5.dev2-py3-none-any.whl", hash = "sha256:ee1ff2c586c53af86da775c287b981f322b3d666a2d4834f13c6b4c77e93abe3"}, - {file = "aioqzone-0.12.5.dev2.tar.gz", hash = "sha256:7f7f71669645116d282a5e3f619196c7f92ffdb04c2494dcab02721c74aaab3b"}, + {file = "aioqzone-0.12.7.dev1-py3-none-any.whl", hash = "sha256:8eaa576ecb05684811c4be86ac0464e188eda38a3f4545477883ea8f3d9fe957"}, + {file = "aioqzone-0.12.7.dev1.tar.gz", hash = "sha256:e779ef4fb6a444c9c881b179d48a8df7a4893232acd7e3056e9bb618aac53c17"}, ] [package.dependencies] @@ -22,18 +22,6 @@ pydantic = ">=1.10.4,<2.0.0" pytz = ">=2022.1,<2023.0" rsa = ">=4.8,<5.0" -[[package]] -name = "aiosqlite" -version = "0.18.0" -description = "asyncio bridge to the standard sqlite3 module" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosqlite-0.18.0-py3-none-any.whl", hash = "sha256:c3511b841e3a2c5614900ba1d179f366826857586f78abd75e7cbeb88e75a557"}, - {file = "aiosqlite-0.18.0.tar.gz", hash = "sha256:faa843ef5fb08bafe9a9b3859012d3d9d6f77ce3637899de20606b7fc39aa213"}, -] - [[package]] name = "alabaster" version = "0.7.13" @@ -198,100 +186,87 @@ files = [ [[package]] name = "charset-normalizer" -version = "3.0.1" +version = "3.1.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." category = "dev" optional = false -python-versions = "*" -files = [ - {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, - {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, - {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, - {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, - {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, - {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, - {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, - {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"}, + {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"}, + {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"}, + {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"}, + {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"}, + {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"}, + {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"}, ] [[package]] @@ -388,80 +363,6 @@ files = [ docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"] testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"] -[[package]] -name = "greenlet" -version = "2.0.2" -description = "Lightweight in-process concurrent programming" -category = "main" -optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" -files = [ - {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"}, - {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"}, - {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, - {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, - {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, - {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, - {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"}, - {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, - {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, - {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, - {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"}, - {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"}, - {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"}, - {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"}, - {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"}, - {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"}, - {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"}, - {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"}, - {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"}, - {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"}, - {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"}, - {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"}, - {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"}, - {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"}, - {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"}, - {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, - {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, - {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, - {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"}, - {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, - {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, - {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, - {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"}, - {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"}, - {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"}, - {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"}, - {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"}, - {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"}, -] - -[package.extras] -docs = ["Sphinx", "docutils (<0.18)"] -test = ["objgraph", "psutil"] - [[package]] name = "h11" version = "0.14.0" @@ -522,14 +423,14 @@ socks = ["socksio (>=1.0.0,<2.0.0)"] [[package]] name = "identify" -version = "2.5.18" +version = "2.5.19" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "identify-2.5.18-py2.py3-none-any.whl", hash = "sha256:93aac7ecf2f6abf879b8f29a8002d3c6de7086b8c28d88e1ad15045a15ab63f9"}, - {file = "identify-2.5.18.tar.gz", hash = "sha256:89e144fa560cc4cffb6ef2ab5e9fb18ed9f9b3cb054384bab4b95c12f6c309fe"}, + {file = "identify-2.5.19-py2.py3-none-any.whl", hash = "sha256:3ee3533e7f6f5023157fbebbd5687bb4b698ce6f305259e0d24b2d7d9efb72bc"}, + {file = "identify-2.5.19.tar.gz", hash = "sha256:4102ecd051f6884449e7359e55b38ba6cd7aafb6ef27b8e2b38495a5723ea106"}, ] [package.extras] @@ -1025,48 +926,48 @@ files = [ [[package]] name = "pydantic" -version = "1.10.5" +version = "1.10.6" description = "Data validation and settings management using python type hints" category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5920824fe1e21cbb3e38cf0f3dd24857c8959801d1031ce1fac1d50857a03bfb"}, - {file = "pydantic-1.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3bb99cf9655b377db1a9e47fa4479e3330ea96f4123c6c8200e482704bf1eda2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2185a3b3d98ab4506a3f6707569802d2d92c3a7ba3a9a35683a7709ea6c2aaa2"}, - {file = "pydantic-1.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f582cac9d11c227c652d3ce8ee223d94eb06f4228b52a8adaafa9fa62e73d5c9"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c9e5b778b6842f135902e2d82624008c6a79710207e28e86966cd136c621bfee"}, - {file = "pydantic-1.10.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:72ef3783be8cbdef6bca034606a5de3862be6b72415dc5cb1fb8ddbac110049a"}, - {file = "pydantic-1.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:45edea10b75d3da43cfda12f3792833a3fa70b6eee4db1ed6aed528cef17c74e"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:63200cd8af1af2c07964546b7bc8f217e8bda9d0a2ef0ee0c797b36353914984"}, - {file = "pydantic-1.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:305d0376c516b0dfa1dbefeae8c21042b57b496892d721905a6ec6b79494a66d"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fd326aff5d6c36f05735c7c9b3d5b0e933b4ca52ad0b6e4b38038d82703d35b"}, - {file = "pydantic-1.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bb0452d7b8516178c969d305d9630a3c9b8cf16fcf4713261c9ebd465af0d73"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9a9d9155e2a9f38b2eb9374c88f02fd4d6851ae17b65ee786a87d032f87008f8"}, - {file = "pydantic-1.10.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f836444b4c5ece128b23ec36a446c9ab7f9b0f7981d0d27e13a7c366ee163f8a"}, - {file = "pydantic-1.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:8481dca324e1c7b715ce091a698b181054d22072e848b6fc7895cd86f79b4449"}, - {file = "pydantic-1.10.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:87f831e81ea0589cd18257f84386bf30154c5f4bed373b7b75e5cb0b5d53ea87"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ce1612e98c6326f10888df951a26ec1a577d8df49ddcaea87773bfbe23ba5cc"}, - {file = "pydantic-1.10.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58e41dd1e977531ac6073b11baac8c013f3cd8706a01d3dc74e86955be8b2c0c"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6a4b0aab29061262065bbdede617ef99cc5914d1bf0ddc8bcd8e3d7928d85bd6"}, - {file = "pydantic-1.10.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:36e44a4de37b8aecffa81c081dbfe42c4d2bf9f6dff34d03dce157ec65eb0f15"}, - {file = "pydantic-1.10.5-cp37-cp37m-win_amd64.whl", hash = "sha256:261f357f0aecda005934e413dfd7aa4077004a174dafe414a8325e6098a8e419"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b429f7c457aebb7fbe7cd69c418d1cd7c6fdc4d3c8697f45af78b8d5a7955760"}, - {file = "pydantic-1.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:663d2dd78596c5fa3eb996bc3f34b8c2a592648ad10008f98d1348be7ae212fb"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51782fd81f09edcf265823c3bf43ff36d00db246eca39ee765ef58dc8421a642"}, - {file = "pydantic-1.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c428c0f64a86661fb4873495c4fac430ec7a7cef2b8c1c28f3d1a7277f9ea5ab"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:76c930ad0746c70f0368c4596020b736ab65b473c1f9b3872310a835d852eb19"}, - {file = "pydantic-1.10.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3257bd714de9db2102b742570a56bf7978e90441193acac109b1f500290f5718"}, - {file = "pydantic-1.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:f5bee6c523d13944a1fdc6f0525bc86dbbd94372f17b83fa6331aabacc8fd08e"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:532e97c35719f137ee5405bd3eeddc5c06eb91a032bc755a44e34a712420daf3"}, - {file = "pydantic-1.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ca9075ab3de9e48b75fa8ccb897c34ccc1519177ad8841d99f7fd74cf43be5bf"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd46a0e6296346c477e59a954da57beaf9c538da37b9df482e50f836e4a7d4bb"}, - {file = "pydantic-1.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3353072625ea2a9a6c81ad01b91e5c07fa70deb06368c71307529abf70d23325"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3f9d9b2be177c3cb6027cd67fbf323586417868c06c3c85d0d101703136e6b31"}, - {file = "pydantic-1.10.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b473d00ccd5c2061fd896ac127b7755baad233f8d996ea288af14ae09f8e0d1e"}, - {file = "pydantic-1.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:5f3bc8f103b56a8c88021d481410874b1f13edf6e838da607dcb57ecff9b4594"}, - {file = "pydantic-1.10.5-py3-none-any.whl", hash = "sha256:7c5b94d598c90f2f46b3a983ffb46ab806a67099d118ae0da7ef21a2a4033b28"}, - {file = "pydantic-1.10.5.tar.gz", hash = "sha256:9e337ac83686645a46db0e825acceea8e02fca4062483f40e9ae178e8bd1103a"}, + {file = "pydantic-1.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9289065611c48147c1dd1fd344e9d57ab45f1d99b0fb26c51f1cf72cd9bcd31"}, + {file = "pydantic-1.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c32b6bba301490d9bb2bf5f631907803135e8085b6aa3e5fe5a770d46dd0160"}, + {file = "pydantic-1.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd9b9e98068fa1068edfc9eabde70a7132017bdd4f362f8b4fd0abed79c33083"}, + {file = "pydantic-1.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c84583b9df62522829cbc46e2b22e0ec11445625b5acd70c5681ce09c9b11c4"}, + {file = "pydantic-1.10.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b41822064585fea56d0116aa431fbd5137ce69dfe837b599e310034171996084"}, + {file = "pydantic-1.10.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:61f1f08adfaa9cc02e0cbc94f478140385cbd52d5b3c5a657c2fceb15de8d1fb"}, + {file = "pydantic-1.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:32937835e525d92c98a1512218db4eed9ddc8f4ee2a78382d77f54341972c0e7"}, + {file = "pydantic-1.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbd5c531b22928e63d0cb1868dee76123456e1de2f1cb45879e9e7a3f3f1779b"}, + {file = "pydantic-1.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e277bd18339177daa62a294256869bbe84df1fb592be2716ec62627bb8d7c81d"}, + {file = "pydantic-1.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89f15277d720aa57e173954d237628a8d304896364b9de745dcb722f584812c7"}, + {file = "pydantic-1.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b243b564cea2576725e77aeeda54e3e0229a168bc587d536cd69941e6797543d"}, + {file = "pydantic-1.10.6-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3ce13a558b484c9ae48a6a7c184b1ba0e5588c5525482681db418268e5f86186"}, + {file = "pydantic-1.10.6-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ac1cd4deed871dfe0c5f63721e29debf03e2deefa41b3ed5eb5f5df287c7b70"}, + {file = "pydantic-1.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:b1eb6610330a1dfba9ce142ada792f26bbef1255b75f538196a39e9e90388bf4"}, + {file = "pydantic-1.10.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4ca83739c1263a044ec8b79df4eefc34bbac87191f0a513d00dd47d46e307a65"}, + {file = "pydantic-1.10.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea4e2a7cb409951988e79a469f609bba998a576e6d7b9791ae5d1e0619e1c0f2"}, + {file = "pydantic-1.10.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53de12b4608290992a943801d7756f18a37b7aee284b9ffa794ee8ea8153f8e2"}, + {file = "pydantic-1.10.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:60184e80aac3b56933c71c48d6181e630b0fbc61ae455a63322a66a23c14731a"}, + {file = "pydantic-1.10.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:415a3f719ce518e95a92effc7ee30118a25c3d032455d13e121e3840985f2efd"}, + {file = "pydantic-1.10.6-cp37-cp37m-win_amd64.whl", hash = "sha256:72cb30894a34d3a7ab6d959b45a70abac8a2a93b6480fc5a7bfbd9c935bdc4fb"}, + {file = "pydantic-1.10.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3091d2eaeda25391405e36c2fc2ed102b48bac4b384d42b2267310abae350ca6"}, + {file = "pydantic-1.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:751f008cd2afe812a781fd6aa2fb66c620ca2e1a13b6a2152b1ad51553cb4b77"}, + {file = "pydantic-1.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12e837fd320dd30bd625be1b101e3b62edc096a49835392dcf418f1a5ac2b832"}, + {file = "pydantic-1.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d92831d0115874d766b1f5fddcdde0c5b6c60f8c6111a394078ec227fca6d"}, + {file = "pydantic-1.10.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:476f6674303ae7965730a382a8e8d7fae18b8004b7b69a56c3d8fa93968aa21c"}, + {file = "pydantic-1.10.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3a2be0a0f32c83265fd71a45027201e1278beaa82ea88ea5b345eea6afa9ac7f"}, + {file = "pydantic-1.10.6-cp38-cp38-win_amd64.whl", hash = "sha256:0abd9c60eee6201b853b6c4be104edfba4f8f6c5f3623f8e1dba90634d63eb35"}, + {file = "pydantic-1.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6195ca908045054dd2d57eb9c39a5fe86409968b8040de8c2240186da0769da7"}, + {file = "pydantic-1.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43cdeca8d30de9a897440e3fb8866f827c4c31f6c73838e3a01a14b03b067b1d"}, + {file = "pydantic-1.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c19eb5163167489cb1e0161ae9220dadd4fc609a42649e7e84a8fa8fff7a80f"}, + {file = "pydantic-1.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:012c99a9c0d18cfde7469aa1ebff922e24b0c706d03ead96940f5465f2c9cf62"}, + {file = "pydantic-1.10.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:528dcf7ec49fb5a84bf6fe346c1cc3c55b0e7603c2123881996ca3ad79db5bfc"}, + {file = "pydantic-1.10.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:163e79386c3547c49366e959d01e37fc30252285a70619ffc1b10ede4758250a"}, + {file = "pydantic-1.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:189318051c3d57821f7233ecc94708767dd67687a614a4e8f92b4a020d4ffd06"}, + {file = "pydantic-1.10.6-py3-none-any.whl", hash = "sha256:acc6783751ac9c9bc4680379edd6d286468a1dc8d7d9906cd6f1186ed682b2b0"}, + {file = "pydantic-1.10.6.tar.gz", hash = "sha256:cf95adb0d1671fc38d8c43dd921ad5814a735e7d9b4d9e437c088002863854fd"}, ] [package.dependencies] @@ -1150,7 +1051,7 @@ files = [ name = "pyyaml" version = "6.0" description = "YAML parser and emitter for Python" -category = "main" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1196,28 +1097,6 @@ files = [ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, ] -[[package]] -name = "qzemoji" -version = "5.1.0" -description = "Translate Qzone emoji link to text." -category = "main" -optional = false -python-versions = ">=3.8,<4.0" -files = [ - {file = "qzemoji-5.1.0-py3-none-any.whl", hash = "sha256:92162f92f37bf2438bc5348d7caa485967e05e5ece1109ef643d7e5efb876e2d"}, - {file = "qzemoji-5.1.0.tar.gz", hash = "sha256:40159c73c4ac1a1853b8b9370d3c0fd6c62e5eb18e268f1c3da8f7a91bcb1a44"}, -] - -[package.dependencies] -httpx = ">=0.23.0,<0.24.0" -PyYAML = ">=6.0,<7.0" -SQLAlchemy = {version = ">=2.0.4,<3.0.0", extras = ["aiosqlite", "asyncio"]} - -[package.source] -type = "legacy" -url = "https://aioqzone.github.io/aioqzone-index/simple" -reference = "aioqzone-index" - [[package]] name = "requests" version = "2.28.2" @@ -1275,14 +1154,14 @@ pyasn1 = ">=0.1.3" [[package]] name = "setuptools" -version = "67.5.0" +version = "67.6.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "setuptools-67.5.0-py3-none-any.whl", hash = "sha256:9f0004c0daa3d41ef4465934a89498da3eef994039f48845d6eb8202aa13b2e9"}, - {file = "setuptools-67.5.0.tar.gz", hash = "sha256:113ff8d482b826d2f3b99f26adb1fe505e526a94a08e68cdf392d1dff9ce0595"}, + {file = "setuptools-67.6.0-py3-none-any.whl", hash = "sha256:b78aaa36f6b90a074c1fa651168723acbf45d14cb1196b6f02c0fd07f17623b2"}, + {file = "setuptools-67.6.0.tar.gz", hash = "sha256:2ee892cd5f29f3373097f5a814697e397cf3ce313616df0af11231e2ad118077"}, ] [package.extras] @@ -1521,49 +1400,6 @@ files = [ lint = ["docutils-stubs", "flake8", "mypy"] test = ["pytest"] -[[package]] -name = "sqlalchemy" -version = "2.0.5" -description = "Database Abstraction Library" -category = "main" -optional = false -python-versions = ">=3.7" -files = [ - {file = "SQLAlchemy-2.0.5-py3-none-any.whl", hash = "sha256:7069df49da29baf2ee71216445f53e8fbedba03b44ea8eb5a11a0b92b12d8c07"}, - {file = "SQLAlchemy-2.0.5.tar.gz", hash = "sha256:721d4ad38be4615d9052d9ed62e91470e46e0ef588fea392ad16f209907fab70"}, -] - -[package.dependencies] -aiosqlite = {version = "*", optional = true, markers = "extra == \"aiosqlite\""} -greenlet = {version = "!=0.4.17", markers = "platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\""} -typing-extensions = [ - {version = ">=4.2.0"}, - {version = "!=3.10.0.1", optional = true, markers = "extra == \"aiosqlite\""}, -] - -[package.extras] -aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] -aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing-extensions (!=3.10.0.1)"] -asyncio = ["greenlet (!=0.4.17)"] -asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] -mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] -mssql = ["pyodbc"] -mssql-pymssql = ["pymssql"] -mssql-pyodbc = ["pyodbc"] -mypy = ["mypy (>=0.910)"] -mysql = ["mysqlclient (>=1.4.0)"] -mysql-connector = ["mysql-connector-python"] -oracle = ["cx-oracle (>=7)"] -oracle-oracledb = ["oracledb (>=1.0.1)"] -postgresql = ["psycopg2 (>=2.7)"] -postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] -postgresql-pg8000 = ["pg8000 (>=1.29.1)"] -postgresql-psycopg = ["psycopg (>=3.0.7)"] -postgresql-psycopg2binary = ["psycopg2-binary"] -postgresql-psycopg2cffi = ["psycopg2cffi"] -pymysql = ["pymysql"] -sqlcipher = ["sqlcipher3-binary"] - [[package]] name = "tomli" version = "2.0.1" @@ -1645,4 +1481,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "87fa8979a783f7891705d4dd8cd0ed9911098e2fe0662bc7a6cfa316483fa3b2" +content-hash = "d7a35085e9d8df784a031f7e0533b97549400adbb3e11b5b9616a711294ff23a" diff --git a/pyproject.toml b/pyproject.toml index 6b29bb1..0aae7c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "aioqzone-feed" -version = "0.12.2.dev1" +version = "0.13.0.dev1" description = "aioqzone plugin providing higher level api for processing feed." authors = ["aioqzone "] license = "AGPL-3.0" @@ -14,8 +14,7 @@ documentation = "https://aioqzone.github.io/aioqzone-feed" [tool.poetry.dependencies] python = "^3.8" -aioqzone = { version = "^0.12.5.dev2", source = "PyPI", allow-prereleases = true } -QzEmoji = { version = "^5.1.0", source = "aioqzone-index" } +aioqzone = { version = "^0.12.7.dev1", source = "PyPI", allow-prereleases = true } # dependency groups @@ -67,4 +66,4 @@ line_length = 99 [tool.black] line-length = 99 -target-version = ['py38'] +target-version = ['py39'] diff --git a/src/aioqzone_feed/api/emoji.py b/src/aioqzone_feed/api/emoji.py deleted file mode 100644 index a07fda3..0000000 --- a/src/aioqzone_feed/api/emoji.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Translate emoji representation to text.""" -import asyncio -import re -from typing import Any, Callable, Coroutine, Dict, List, Type, Union - -import qzemoji as qe -from aioqzone.type.entity import AtEntity, ConEntity, TextEntity -from lxml.html import HtmlElement, fromstring -from qzemoji.utils import build_tag - -from ..type import FeedContent - -TAG_RE = re.compile(r"\[em\]e(\d+)\[/em\]") -URL_RE = re.compile(r"https?://[\w\.]+/qzone/em/e(\d+)\.\w{3}") - -t2a: Dict[Type[ConEntity], List[str]] = { - TextEntity: ["con"], - AtEntity: ["nick"], -} - - -async def query_wrap(eid: int, fmt="[/{name}]") -> str: - """This function query the given eid and wraps it if it is not a pure emoji word. If the name is not - stored in the database, then it will return a ``[/em]`` tag. - - :return: a ``[/em]`` tag if query has no result, otherwise a wrapped name. - - .. seealso:: :meth:`wrap_plain_text`, :meth:`~qzemoji.utils.build_tag` - """ - name = await qe.query(eid) - if name is None: - return build_tag(eid) - return wrap_plain_text(name, fmt=fmt) - - -def wrap_plain_text(name: str, fmt="[/{name}]") -> str: - """This function wraps the given `name` with the given `fmt`, if it is not a pure emoji word. - - :param name: the customized emoji name. - :param fmt: a format string in ``{`` style, default as ``[/{name}]``. - :return: The emoji itself if it is a pure emoji word, otherwise a string wrapped by the `fmt`. - """ - if re.fullmatch(r"[^\u0000-\uFFFF]*", name): - return name - return fmt.format(name=name) - - -async def sub( - pattern: re.Pattern, repl: Callable[[re.Match], Coroutine[Any, Any, str]], text: str -): - """ - The sub function is a async-version of :meth:`re.sub`. - It works like pattern.sub(repl, text), except for this `repl` is an async-function. - - :param pattern: regex expression. - :param repl: Used to replace the matched text. Input an :class:`re.Match`, returns a string, async. - :param text: String to be searched. - :return: replaced string. - """ - r, tasks = [], [] - base = 0 - for i, m in enumerate(pattern.finditer(text)): - fr, to = m.span(0) - r.append(text[base:fr]) # save un-replaced string as is - task = asyncio.create_task(repl(m)) - task.add_done_callback(lambda t, idx=i: r.__setitem__(idx, r[idx] + t.result())) - tasks.append(task) - base = to - r.append(text[base:]) - if tasks: - done, pending = await asyncio.wait(tasks) - assert not pending - excs = list(filter(None, (i.exception() for i in done))) - if excs: - raise RuntimeError(*excs) - return "".join(r) - - -async def trans_tag(text: str): - return await sub(TAG_RE, lambda m: query_wrap(int(m.group(1))), text) - - -async def trans_detail(feed: FeedContent) -> FeedContent: - tasks = [] - - def repl_attr(obj: object, attr: str): - t = asyncio.create_task(trans_tag(getattr(obj, attr))) - t.add_done_callback(lambda t: setattr(obj, attr, t.result())) - tasks.append(t) - - repl_attr(feed, "nickname") - if feed.entities: - for e in feed.entities: - al = t2a.get(type(e), None) - if al is None: - continue - for a in al: - repl_attr(e, a) - - if tasks: - done, pending = await asyncio.wait(tasks) - assert not pending - excs = list(filter(None, (i.exception() for i in done))) - if excs: - raise RuntimeError(*excs) - return feed - - -async def trans_html(txtbox: Union[HtmlElement, str]) -> HtmlElement: - if isinstance(txtbox, str): - txtbox = fromstring(txtbox) - assert isinstance(txtbox, HtmlElement) - imgs: List[HtmlElement] = txtbox.cssselect("img") - - tasks = [] - for i in imgs: - m = URL_RE.match(i.get("src", "")) - if not m: - continue - t = asyncio.create_task(query_wrap(int(m.group(1)))) - t.add_done_callback( - lambda t, e=i: setattr(e, "tail", t.result() + (e.tail or "")) or e.drop_tree() - ) - tasks.append(t) - - if tasks: - done, pending = await asyncio.wait(tasks) - assert not pending - excs = list(filter(None, (i.exception() for i in done))) - if excs: - raise RuntimeError(*excs) - return txtbox diff --git a/src/aioqzone_feed/api/feed/__init__.py b/src/aioqzone_feed/api/feed/__init__.py new file mode 100644 index 0000000..a6b887e --- /dev/null +++ b/src/aioqzone_feed/api/feed/__init__.py @@ -0,0 +1 @@ +from .web import FeedWebApi as FeedApi diff --git a/src/aioqzone_feed/api/feed/h5.py b/src/aioqzone_feed/api/feed/h5.py new file mode 100644 index 0000000..4462bdc --- /dev/null +++ b/src/aioqzone_feed/api/feed/h5.py @@ -0,0 +1,170 @@ +import asyncio +import logging +import time +from typing import Optional + +from aioqzone.api import Loginable +from aioqzone.api.h5 import QzoneH5API +from aioqzone.exception import LoginError, QzoneError +from aioqzone.type.resp.h5 import FeedData +from httpx import HTTPStatusError +from qqqr.event import Emittable, hook_guard +from qqqr.exception import UserBreak +from qqqr.utils.net import ClientAdapter + +from aioqzone_feed.api.heartbeat import HeartbeatApi +from aioqzone_feed.event import FeedEvent +from aioqzone_feed.type import FeedContent + +log = logging.getLogger(__name__) +login_exc = (LoginError, UserBreak, asyncio.CancelledError) + + +class FeedH5Api(QzoneH5API, Emittable[FeedEvent]): + """ + .. versionadded:: 0.13.0 + """ + + def __init__(self, client: ClientAdapter, loginman: Loginable, *, init_hb=True): + super().__init__(client, loginman) + self.bid = -1 + if init_hb: + self.hb_api = HeartbeatApi(self) + + def new_batch(self) -> FeedEvent.TY_BID: + """ + The new_batch function edit internal batch id and return it. + + A batch id can be used to identify a batch, thus even the same feed can have different id e.g. `(bid, uin, abstime)`. + + :rtype: :obj:`FeedEvent.TY_BID` + :return: The batch_id. + """ + + self.bid += 1 + return self.bid + + async def get_feeds_by_count(self, count: int = 10) -> int: + """Get feeds by count. + + :param count: feeds count to get, max as 10, defaults to 10 + + :raises `qqqr.exception.UserBreak`: qr login canceled + :raises `aioqzone.exception.LoginError`: not logined + :raises `SystemExit`: unexcpected error + + :return: feeds num got actually. + + ..note:: You may need :meth:`.new_batch` to generate a new batch id. + """ + stop_fetching = False + got = 0 + aux = "" + exceed_pred = hook_guard(self.hook.StopFeedFetch) + for page in range(1000): + try: + resp = await self.get_active_feeds(aux) + except (QzoneError, HTTPStatusError) as e: + log.warning(f"Error when fetching page. Skipped. {e}") + continue + aux = resp.attachinfo + for fd in resp.vFeeds[: count - got]: + if await exceed_pred(fd): + stop_fetching = True + continue + self._dispatch_feed(fd) + got += 1 + if stop_fetching or got >= count or not resp.hasmore: + break + return got + + async def get_feeds_by_second( + self, + seconds: float, + start: Optional[float] = None, + ) -> int: + """Get feeds by abstime (seconds). Range: `[start - second, start]`. + + :param seconds: filter on abstime, calculate from `start`. + :param start: start timestamp, defaults to None, means now. + :param exceed_pred: another criterion to judge if the feed is out of range, defaults to None + + :raises `qqqr.exception.UserBreak`: qr login canceled + :raises `aioqzone.exception.LoginError`: not logined + :raises `SystemExit`: unexcpected error + + :return: feeds num got actually. + + ..note:: You may need :meth:`.new_batch` to generate a new batch id. + """ + start = start or time.time() + end = start - seconds + stop_fetching = False + got = 0 + aux = "" + exceed_pred = hook_guard(self.hook.StopFeedFetch) + for page in range(1000): + try: + resp = await self.get_active_feeds(aux) + except (QzoneError, HTTPStatusError) as e: + log.warning(f"Error when fetching page. Skipped. {e}") + continue + aux = resp.attachinfo + for fd in resp.vFeeds: + if fd.abstime > start: + continue + if fd.abstime < end or await exceed_pred(fd): + stop_fetching = True + continue + self._dispatch_feed(fd) + got += 1 + if stop_fetching or not resp.hasmore: + break + return got + + def drop_rule(self, feed: FeedData) -> bool: + """Drop feeds according to some rules. + No need to emit :meth:`FeedEvent.FeedDropped` event, it is handled by :meth:`_dispatch_feed`. + + Subclasses may inherit this method to customize their own rules. + + :param feed: the feed + :return: if the feed is dropped. + """ + if feed.userinfo.uin == 20050606: + log.info(f"advertisement rule hit: {feed.userinfo.uin}") + log.debug(f"Dropped: {feed}") + return True + + if feed.cellid.startswith("advertisement"): + log.info(f"advertisement rule hit: {feed.cellid}") + log.debug(f"Dropped: {feed}") + return True + + return False + + def _dispatch_feed(self, feed: FeedData) -> None: + """dispatch feed according to api support. + + 1. Drop feed according to rules defined in `drop_rule`, trigger :meth:`FeedDropped` hook if dropped; + 2. Get more if `hasmore` flag is set to ``1`` (Not Implemented yet); + 3. Trigger :meth:`FeedProcEnd` for prcocessed feeds. + + :param feed: feed + """ + model = FeedContent.from_feed(feed) + + if self.drop_rule(feed): + FeedContent.from_feed(feed) + self.add_hook_ref("dispatch", self.hook.FeedDropped(self.bid, model)) + return + + model.set_detail(feed) + self.add_hook_ref("hook", self.hook.FeedProcEnd(self.bid, model)) + + def stop(self) -> None: + """Clear **all** registered tasks. All tasks will be CANCELLED if not finished.""" + log.warning("FeedApi stopping...") + self.clear(*self._tasks.keys()) + if hasattr(self, "hb_api"): + self.hb_api.stop() diff --git a/src/aioqzone_feed/api/feed.py b/src/aioqzone_feed/api/feed/web.py similarity index 89% rename from src/aioqzone_feed/api/feed.py rename to src/aioqzone_feed/api/feed/web.py index 65422c5..5ae5190 100644 --- a/src/aioqzone_feed/api/feed.py +++ b/src/aioqzone_feed/api/feed/web.py @@ -15,10 +15,9 @@ from qqqr.exception import HookError, UserBreak from qqqr.utils.net import ClientAdapter -from ..event import FeedEvent -from ..type import FeedContent, VisualMedia -from .emoji import trans_detail, trans_html -from .heartbeat import HeartbeatApi +from aioqzone_feed.api.heartbeat import HeartbeatApi +from aioqzone_feed.event import FeedEvent +from aioqzone_feed.type import FeedContent, VisualMedia log = logging.getLogger(__name__) login_exc = (LoginError, UserBreak, asyncio.CancelledError) @@ -64,7 +63,7 @@ def safe_unpack(task): return task -class FeedApi(QzoneWebAPI, Emittable[FeedEvent]): +class FeedWebApi(QzoneWebAPI, Emittable[FeedEvent]): def __init__(self, client: ClientAdapter, loginman: Loginable, *, init_hb=True): super().__init__(client, loginman) self.bid = -1 @@ -182,13 +181,11 @@ def drop_rule(self, feed: FeedRep) -> bool: if feed.uin == 20050606: log.info(f"advertisement rule hit: {feed.uin}") log.debug(f"Dropped: {feed}") - self.add_hook_ref("dispatch", self.hook.FeedDropped(self.bid, feed)) return True if feed.fid.startswith("advertisement"): log.info(f"advertisement rule hit: {feed.fid}") log.debug(f"Dropped: {feed}") - self.add_hook_ref("dispatch", self.hook.FeedDropped(self.bid, feed)) return True return False @@ -207,17 +204,19 @@ def _dispatch_feed(self, feed: FeedRep) -> None: :param feed: feed """ + model = FeedContent.from_feed(feed) + if self.drop_rule(feed): + self.add_hook_ref("dispatch", self.hook.FeedDropped(self.bid, model)) return - model = FeedContent.from_feedrep(feed) has_cur = [311] try: root, htmlinfo = HtmlInfo.from_html(feed.html) except ValidationError: log.debug("HtmlInfo ValidationError, html=%s", feed.html, exc_info=True) - self.add_hook_ref("dispatch", self.hook.FeedDropped(self.bid, feed)) + self.add_hook_ref("dispatch", self.hook.FeedDropped(self.bid, model)) return model.set_frominfo(htmlinfo) @@ -239,10 +238,7 @@ def detail_procs(dt: Optional[FeedDetailRep]): return self.__default_dispatch(feed, model, htmlinfo, root) model.set_detail(dt) - add_done_callback( - self.add_hook_ref("dispatch", trans_detail(model)), - lambda t: self.add_hook_ref("hook", self.hook.FeedProcEnd(self.bid, model)), - ) + self.add_hook_ref("hook", self.hook.FeedProcEnd(self.bid, model)) get_full = self.add_hook_ref("dispatch", self.emotion_msgdetail(feed.uin, feed.fid)) add_done_callback(get_full, detail_procs) @@ -253,32 +249,32 @@ def __default_dispatch( """Default feed processing: Parse info from html feed. If media is detected, then request for `floatview_photo_list` album api. """ + # has to parse html now # TODO: HtmlContent.from_html is risky def html_content_procs(root: HtmlElement): htmlct = HtmlContent.from_html(root, feed.uin) - model.set_fromhtml(htmlct, forward=htmlinfo.unikey) + model.set_detail(htmlct) + if htmlinfo.unikey: + model.forward = htmlinfo.unikey self.add_hook_ref("hook", self.hook.FeedProcEnd(self.bid, model)) self._add_mediaupdate_task(model, htmlct) if htmlinfo.complete: - add_done_callback( - self.add_hook_ref("dispatch", trans_html(root)), - lambda trans_root: html_content_procs(trans_root or root), - ) + html_content_procs(root) return - def full_html_procs(full: str): - full_root = fromstring(full) - add_done_callback( - self.add_hook_ref("dispatch", trans_html(full_root)), - lambda trans_root: html_content_procs(trans_root or full_root), - ) + def full_html_procs(full: Optional[str]): + if full: + full_root = fromstring(full) + else: + full_root = root + html_content_procs(full_root) get_full = self.add_hook_ref( "dispatch", self.emotion_getcomments(feed.uin, feed.fid, htmlinfo.feedstype) ) - add_done_callback(get_full, lambda full: full and full_html_procs(full)) + add_done_callback(get_full, full_html_procs) def _add_mediaupdate_task(self, model: FeedContent, content: HtmlContent) -> None: if not (content.album and content.pic): @@ -323,7 +319,7 @@ async def __fv_retry( return else: return - model.media = [VisualMedia.from_picrep(PicRep.from_floatview(i)) for i in fv] + model.media = [VisualMedia.from_pic(PicRep.from_floatview(i)) for i in fv] self.add_hook_ref("hook", self.hook.FeedMediaUpdate(bid, model)) def stop(self) -> None: diff --git a/src/aioqzone_feed/api/heartbeat.py b/src/aioqzone_feed/api/heartbeat.py index 7f6c449..54a9064 100644 --- a/src/aioqzone_feed/api/heartbeat.py +++ b/src/aioqzone_feed/api/heartbeat.py @@ -1,27 +1,50 @@ import asyncio import logging -from functools import partial -from typing import Optional +from functools import partial, singledispatch +from typing import Optional, Union from aioqzone.api import QzoneWebAPI +from aioqzone.api.h5 import QzoneH5API from aioqzone.event import LoginMethod from aioqzone.exception import LoginError, QzoneError, SkipLoginInterrupt +from aioqzone.type.resp import FeedsCount as WebFeedsCount +from aioqzone.type.resp.h5 import FeedCount as H5FeedsCount from httpx import HTTPError, HTTPStatusError from qqqr.event import Emittable from qqqr.exception import HookError -from ..event import HeartbeatEvent -from ..utils.task import AsyncTimer +from aioqzone_feed.event import HeartbeatEvent +from aioqzone_feed.utils.task import AsyncTimer log = logging.getLogger(__name__) +@singledispatch +def new_feed_cnt(result) -> int: + raise TypeError(result, type(result)) + + +@new_feed_cnt.register +def _friendFeeds_new_cnt(result: WebFeedsCount): + return result.friendFeeds_new_cnt + + +@new_feed_cnt.register +def _active_cnt(result: H5FeedsCount): + return result.active_cnt + + class HeartbeatApi(Emittable[HeartbeatEvent]): hb_timer = None - def __init__(self, api: QzoneWebAPI) -> None: + def __init__(self, api: Union[QzoneH5API, QzoneWebAPI]) -> None: super().__init__() - self.api = api + if isinstance(api, QzoneH5API): + self.hb_api = api.mfeeds_get_count + elif isinstance(api, QzoneWebAPI): + self.hb_api = api.get_feeds_count + else: + raise TypeError("wrong api instance:", type(api)) async def heartbeat_refresh(self, *, retry: int = 2, retry_intv: float = 5): """A wrapper function that calls :external:meth:`aioqzone.api.QzoneWebAPI.get_feeds_count` @@ -39,7 +62,7 @@ async def heartbeat_refresh(self, *, retry: int = 2, retry_intv: float = 5): r = False for i in range(retry): try: - cnt = (await self.api.get_feeds_count()).friendFeeds_new_cnt + cnt = new_feed_cnt(await self.hb_api()) log.debug("heartbeat: friendFeeds_new_cnt=%d", cnt) if cnt: self.add_hook_ref("hook", self.hook.HeartbeatRefresh(cnt)) diff --git a/src/aioqzone_feed/event/feed.py b/src/aioqzone_feed/event/feed.py index e3f6d7e..52c14c0 100644 --- a/src/aioqzone_feed/event/feed.py +++ b/src/aioqzone_feed/event/feed.py @@ -1,20 +1,25 @@ from typing import Union from aioqzone.type.resp import FeedRep +from aioqzone.type.resp.h5 import FeedData from qqqr.event import Event -from ..type import FeedContent +from aioqzone_feed.type import BaseFeed, FeedContent class FeedEvent(Event): TY_BID = int - async def FeedDropped(self, bid: TY_BID, feed: Union[FeedRep, FeedContent]): + async def FeedDropped(self, bid: TY_BID, feed: BaseFeed): """ The FeedDropped hook is called when a feed is dropped for hitting some rules (e.g. advertisement) :param bid: Used to identify feed batch (tell from different calling). :param feed: Used to pass a ref to the feed. + + .. versionchanged:: 0.12.3 + + `feed` is a :class:`BaseFeed` type. """ pass @@ -42,7 +47,7 @@ async def FeedMediaUpdate(self, bid: TY_BID, feed: FeedContent): pass - async def StopFeedFetch(self, feed: FeedRep) -> bool: + async def StopFeedFetch(self, feed: Union[FeedData, FeedRep]) -> bool: """Used to judge if a feed fetching loop should break. Once this hook returns `True`, new pages will not be fetched any more. Note that the rest feeds of current page may still trigger `FeedProcEnd`. @@ -50,5 +55,9 @@ async def StopFeedFetch(self, feed: FeedRep) -> bool: This will also be used in `FeedApi.get_feeds_by_count`. .. versionadded:: 0.12.0 + + .. versionchanged:: 0.12.3 + + `feed` might be a :class:`FeedData` for web/h5 api compatability. """ return False diff --git a/src/aioqzone_feed/type.py b/src/aioqzone_feed/type.py index f656abb..10c0f50 100644 --- a/src/aioqzone_feed/type.py +++ b/src/aioqzone_feed/type.py @@ -1,22 +1,47 @@ +import sys +from functools import singledispatchmethod from typing import List, Optional, Union, cast from aioqzone.type.entity import ConEntity from aioqzone.type.internal import LikeData from aioqzone.type.resp import FeedDetailRep, FeedRep, PicRep, VideoInfo, VideoRep +from aioqzone.type.resp.h5 import FeedData, FeedOriginal, FeedVideo, PicData, Share +from aioqzone.utils.entity import split_entities from aioqzone.utils.html import HtmlContent, HtmlInfo from aioqzone.utils.time import approx_ts -from pydantic import BaseModel, HttpUrl +from pydantic import BaseModel, Field, HttpUrl +from typing_extensions import Self + +if sys.version_info < (3, 9): + # python 3.8 patch for singledispatchmethod + # https://github.com/python/cpython/issues/83860 + # workaround: https://github.com/python/cpython/issues/83860#issuecomment-1093857837 + + def _register(self, cls, method=None): + if hasattr(cls, "__func__"): + setattr(cls, "__annotations__", cls.__func__.__annotations__) + return self.dispatcher.register(cls, func=method) + + singledispatchmethod.register = _register class VisualMedia(BaseModel): height: int width: int - thumbnail: HttpUrl + thumbnail: Optional[HttpUrl] = None raw: HttpUrl is_video: bool + class Config: + keep_untouched = (singledispatchmethod,) + + @singledispatchmethod + def from_pic(cls, pic): + raise TypeError(pic) + + @from_pic.register @classmethod - def from_picrep(cls, pic: PicRep): + def _(cls, pic: PicRep): if pic.is_video: assert isinstance(pic, VideoRep) return cls.from_video(pic.video_info) @@ -29,8 +54,30 @@ def from_picrep(cls, pic: PicRep): is_video=False, ) + @from_pic.register @classmethod - def from_video(cls, video: VideoInfo): + def _(cls, pic: PicData): + if pic.videodata: + return cls.from_video(pic.videodata) + + raw = pic.photourl.largest + thumb = pic.photourl.smallest + return cls( + is_video=False, + height=pic.origin_height, + width=pic.origin_width, + raw=raw.url, + thumbnail=thumb.url, + ) + + @singledispatchmethod + @classmethod + def from_video(cls, video): + raise TypeError(video) + + @from_video.register + @classmethod + def _(cls, video: VideoInfo): return cls( height=video.cover_height, width=video.cover_width, @@ -39,12 +86,24 @@ def from_video(cls, video: VideoInfo): is_video=True, ) + @from_video.register + @classmethod + def _(cls, video: FeedVideo): + cover = video.coverurl.largest + return cls( + height=cover.height, + width=cover.width, + thumbnail=cover.url, + raw=video.videourl, + is_video=True, + ) + class BaseFeed(BaseModel): """FeedModel is a model for storing a feed, with the info to hashing and retrieving the feed.""" appid: int - typeid: int + typeid: int = 0 fid: str """Feed id, a hex string with 24/32 chars, or a much shorter placeholder. @@ -72,6 +131,7 @@ class BaseFeed(BaseModel): class Config: orm_mode = True + keep_untouched = (singledispatchmethod,) def __hash__(self) -> int: return hash((self.uin, self.abstime)) @@ -97,8 +157,14 @@ def __lt__(self, o: "BaseFeed"): def __repr__(self) -> str: return f"{self.__class__.__name__}(uin={self.uin},abstime={self.abstime}')" + @singledispatchmethod @classmethod - def from_feedrep(cls, obj: FeedRep, **kwds): + def from_feed(cls, obj, **kwds) -> Self: + raise TypeError(obj) + + @from_feed.register + @classmethod + def _(cls, obj: FeedRep, **kwds): return cls( appid=obj.appid, typeid=obj.typeid, @@ -109,15 +175,39 @@ def from_feedrep(cls, obj: FeedRep, **kwds): **kwds, ) + @from_feed.register + @classmethod + def _(cls, obj: FeedData, **kwds): + return cls( + appid=obj.common.appid, + typeid=obj.common.typeid, + fid=obj.cellid, + abstime=obj.abstime, + uin=obj.userinfo.uin, + nickname=obj.userinfo.nickname, + **kwds, + ) + class BaseDetail(BaseModel): - entities: Optional[List[ConEntity]] = None + entities: List[ConEntity] = Field(default_factory=list) forward: Union[HttpUrl, str, BaseFeed, None] = None """unikey to the feed, or the content itself.""" - media: Optional[List[VisualMedia]] = None + media: List[VisualMedia] = Field(default_factory=list) + + class Config: + keep_untouched = (singledispatchmethod,) + + @singledispatchmethod + def set_detail(self, obj) -> None: + raise TypeError("Invalid type", type(obj)) + + @set_detail.register + def _set_detail_from_detail(self, obj: FeedDetailRep): + self.entities = obj.entities or [] + if isinstance(self, BaseFeed): + self.nickname = self.nickname or obj.name - def set_detail(self, obj: FeedDetailRep): - self.entities = cast(Optional[list], obj.entities) if obj.rt_uin: assert obj.rt_con unikey = LikeData.persudo_unikey(311, obj.rt_uin, fid=obj.rt_fid) @@ -130,15 +220,15 @@ def set_detail(self, obj: FeedDetailRep): abstime=approx_ts(obj.rt_createTime) if obj.rt_createTime else 0, curkey=unikey, unikey=unikey, - entities=cast(Optional[list], obj.rt_con.entities), + entities=obj.rt_con.entities or [], ) if obj.pic: assert all(i.valid_url() for i in obj.pic) if self.forward is None: - self.media = [VisualMedia.from_picrep(i) for i in obj.pic] + self.media = [VisualMedia.from_pic(i) for i in obj.pic] else: assert isinstance(self.forward, FeedContent) - self.forward.media = [VisualMedia.from_picrep(i) for i in obj.pic] + self.forward.media = [VisualMedia.from_pic(i) for i in obj.pic] if obj.video: if self.forward is None: @@ -149,10 +239,40 @@ def set_detail(self, obj: FeedDetailRep): self.forward.media = self.forward.media or [] self.forward.media.extend(VisualMedia.from_video(i) for i in obj.video) - def set_fromhtml(self, obj: HtmlContent, forward: Optional[Union[HttpUrl, str]] = None): - self.entities = cast(Optional[list], obj.entities) - self.forward = forward - self.media = [VisualMedia.from_picrep(i) for i in obj.pic] if obj.pic else None + @set_detail.register + def _set_detail_from_feeddata(self, obj: FeedData): + self.entities = split_entities(obj.summary.summary) + if obj.original: + if isinstance(obj.original, FeedOriginal): + org = obj.original + self.forward = FeedContent( + entities=split_entities(org.summary.summary), + appid=org.common.appid, + typeid=org.common.typeid, + fid=org.cellid, + abstime=org.common.time, + uin=org.userinfo.uin, + nickname=org.userinfo.nickname, + curkey=org.common.curkey, + unikey=org.common.orgkey, + ) + if org.pic: + self.forward.media = [VisualMedia.from_pic(i) for i in org.pic.picdata] + + elif isinstance(obj.original, Share): + self.forward = obj.original.common.orgkey + + if obj.pic: + self.media = [VisualMedia.from_pic(i) for i in obj.pic.picdata] + if obj.video: + self.media.append(VisualMedia.from_video(obj.video)) + + @set_detail.register + def set_fromhtml(self, obj: HtmlContent): + if obj.entities: + self.entities = obj.entities + if obj.pic: + self.media = [VisualMedia.from_pic(i) for i in obj.pic] class FeedContent(BaseFeed, BaseDetail): @@ -174,7 +294,3 @@ def set_frominfo(self, info: HtmlInfo): def __repr__(self) -> str: return super().__repr__() + f'(content="{self.entities}",#media={len(self.media or "0")})' - - def set_detail(self, obj: FeedDetailRep): - self.nickname = self.nickname or obj.name - return super().set_detail(obj) diff --git a/test/api/conftest.py b/test/api/conftest.py index 9b13577..566750f 100644 --- a/test/api/conftest.py +++ b/test/api/conftest.py @@ -42,6 +42,7 @@ async def QrFetched(self, png: bytes, times: int): int(env["TEST_UIN"]), strategy_to_order[env.get("TEST_QRSTRATEGY", "forbid")], # forbid QR by default. pwd=env.get("TEST_PASSWORD", None), + h5=True, ) yield man diff --git a/test/api/test_emoji.py b/test/api/test_emoji.py deleted file mode 100644 index 1c1d827..0000000 --- a/test/api/test_emoji.py +++ /dev/null @@ -1,25 +0,0 @@ -import pytest -import qzemoji as qe - -pytestmark = pytest.mark.asyncio - -qe.enable_auto_update = False - - -async def test_tag(): - from aioqzone_feed.api.emoji import trans_tag - - assert "[/微笑][/撇嘴][/色]" == await trans_tag("[em]e100[/em][em]e101[/em][em]e102[/em]") - assert "阿巴阿巴🐷啊对对🐷对" == await trans_tag("阿巴阿巴[em]e400343[/em]啊对对[em]e400343[/em]对") - assert "[em]e1111111[/em]" == await trans_tag("[em]e1111111[/em]") - - -async def test_html(): - html1 = "
" - html2 = "
阿巴阿巴啊对对
" - from lxml.html import fromstring - - from aioqzone_feed.api.emoji import trans_html - - assert "[/微笑][/撇嘴][/色]" == (await trans_html(fromstring(html1))).text_content() - assert "阿巴阿巴🐷啊对对🐷对" == (await trans_html(fromstring(html2))).text_content() diff --git a/test/api/test_h5.py b/test/api/test_h5.py new file mode 100644 index 0000000..904e3f3 --- /dev/null +++ b/test/api/test_h5.py @@ -0,0 +1,73 @@ +import pytest +import pytest_asyncio +from aioqzone.api.loginman import MixedLoginMan +from aioqzone.exception import LoginError +from qqqr.utils.net import ClientAdapter + +from aioqzone_feed.api.feed.h5 import FeedH5Api as FeedApi +from aioqzone_feed.event import FeedEvent +from aioqzone_feed.type import FeedContent + +pytestmark = pytest.mark.asyncio + + +@pytest_asyncio.fixture(scope="module") +async def man(man: MixedLoginMan): + man.h5() + return man + + +class FeedEvent4Test(FeedEvent): + def __init__(self) -> None: + super().__init__() + self.batch = [] + self.drop = [] + + async def FeedProcEnd(self, bid: int, feed: FeedContent): + self.batch.append(feed) + assert feed.nickname + assert feed.appid + assert feed.fid + + async def FeedMediaUpdate(self, feed: FeedContent): + assert feed.media + + async def FeedDropped(self, bid: int, feed): + self.drop.append(bid) + + +@pytest_asyncio.fixture(scope="module") +async def api(client: ClientAdapter, man: MixedLoginMan): + api = FeedApi(client, man, init_hb=False) + api.register_hook(FeedEvent4Test()) + yield api + api.stop() + + +async def test_by_count(api: FeedApi): + hook = api.hook + assert isinstance(hook, FeedEvent4Test) + try: + n = await api.get_feeds_by_count(10) + except LoginError as e: + pytest.skip(str(e)) + done, pending = await api.wait("hook", "dispatch") + assert not pending + assert len(hook.batch) == n - len(hook.drop) + assert len(set(hook.batch)) == n - len(hook.drop) + api.clear() + hook.batch.clear() + + +async def test_by_second(api: FeedApi): + hook = api.hook + assert isinstance(hook, FeedEvent4Test) + try: + n = await api.get_feeds_by_second(3 * 86400) + except LoginError as e: + pytest.skip(str(e)) + done, pending = await api.wait("hook", "dispatch") + assert not pending + assert len(set(hook.batch)) == len(hook.batch) + api.clear() + hook.batch.clear() diff --git a/test/api/test_heartbeat.py b/test/api/test_heartbeat.py index 1f5eebc..9aac8a6 100644 --- a/test/api/test_heartbeat.py +++ b/test/api/test_heartbeat.py @@ -4,7 +4,7 @@ import pytest import pytest_asyncio -from aioqzone.api import QzoneWebAPI +from aioqzone.api import QzoneH5API from aioqzone.api.loginman import MixedLoginMan from aioqzone.event import LoginMethod from aioqzone.exception import LoginError, QzoneError, SkipLoginInterrupt @@ -21,7 +21,7 @@ @pytest_asyncio.fixture(scope="module") async def api(client: ClientAdapter, man: MixedLoginMan): - api = HeartbeatApi(QzoneWebAPI(client, man)) + api = HeartbeatApi(QzoneH5API(client, man)) api.register_hook(HeartbeatEvent()) yield api api.stop() @@ -45,9 +45,7 @@ async def api(client: ClientAdapter, man: MixedLoginMan): ], ) async def test_heartbeat_exc(api: HeartbeatApi, exc2r: Type[BaseException], should_alive: bool): - from aioqzone.api import QzoneWebAPI - - with patch.object(QzoneWebAPI, "get_feeds_count", side_effect=exc2r): + with patch.object(api, "hb_api", side_effect=exc2r): api.add_heartbeat(retry=2, hb_intv=0.1, retry_intv=0) assert api.hb_timer await asyncio.sleep(0.4) diff --git a/test/api/test_types.py b/test/api/test_types.py new file mode 100644 index 0000000..8d98d09 --- /dev/null +++ b/test/api/test_types.py @@ -0,0 +1,8 @@ +from aioqzone.utils.html import HtmlContent + +from aioqzone_feed.type import BaseDetail + + +def test_set_detail(): + detail = BaseDetail() + detail.set_detail(HtmlContent(entities=[], pic=[], album=None)) diff --git a/test/api/test_feed.py b/test/api/test_web.py similarity index 96% rename from test/api/test_feed.py rename to test/api/test_web.py index bb4bde7..6227570 100644 --- a/test/api/test_feed.py +++ b/test/api/test_web.py @@ -4,7 +4,7 @@ from aioqzone.exception import LoginError from qqqr.utils.net import ClientAdapter -from aioqzone_feed.api.feed import FeedApi +from aioqzone_feed.api.feed.web import FeedWebApi as FeedApi from aioqzone_feed.event import FeedEvent from aioqzone_feed.type import FeedContent diff --git a/test/test_task.py b/test/test_task.py index 1be3c69..b099419 100644 --- a/test/test_task.py +++ b/test/test_task.py @@ -41,10 +41,12 @@ async def inc(): timer = AsyncTimer(0.1, inc) assert timer.state == "INIT" timer() + assert timer.task assert timer.state == "PENDING" assert timer.last_call == 0 await asyncio.sleep(1) assert timer.state == "FINISHED" + timer.task.exception() assert timer.last_call > 0 assert cnt == 4 @@ -65,9 +67,11 @@ async def inc(): for _ in range(2): cnt = 0 timer() + assert timer.task assert timer.state == "PENDING" await asyncio.sleep(1) assert timer.state == "FINISHED" + timer.task.exception() assert cnt == 4 assert entr == 2