From 7afb4823c06c982e352943b8a9304c20b8a04924 Mon Sep 17 00:00:00 2001 From: Juanjo Salvador Date: Wed, 17 Jul 2024 21:34:25 +0000 Subject: [PATCH 1/4] feat(all): add Poetry support --- NyaaPy/__init__.py | 7 - nyaapy/__init__.py | 7 + {NyaaPy => nyaapy}/nyaa.py | 66 ++-- {NyaaPy => nyaapy}/sukebei.py | 32 +- {NyaaPy => nyaapy}/torrent.py | 0 {NyaaPy => nyaapy}/utils.py | 183 ++++++----- poetry.lock | 602 ++++++++++++++++++++++++++++++++++ pyproject.toml | 23 ++ setup.py | 40 +-- tests/test.py | 41 +-- tests/test_pantsu.py | 1 + tests/test_sukebei.py | 25 +- 12 files changed, 827 insertions(+), 200 deletions(-) delete mode 100644 NyaaPy/__init__.py create mode 100644 nyaapy/__init__.py rename {NyaaPy => nyaapy}/nyaa.py (51%) rename {NyaaPy => nyaapy}/sukebei.py (55%) rename {NyaaPy => nyaapy}/torrent.py (100%) rename {NyaaPy => nyaapy}/utils.py (62%) create mode 100644 poetry.lock create mode 100644 pyproject.toml diff --git a/NyaaPy/__init__.py b/NyaaPy/__init__.py deleted file mode 100644 index 0299c47..0000000 --- a/NyaaPy/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# Info about the module -__version__ = '0.6.3' -__author__ = 'Juanjo Salvador' -__email__ = 'juanjosalvador@netc.eu' -__url__ = 'http://juanjosalvador.me' -__copyright__ = '2017 Juanjo Salvador' -__license__ = 'MIT license' \ No newline at end of file diff --git a/nyaapy/__init__.py b/nyaapy/__init__.py new file mode 100644 index 0000000..383c41b --- /dev/null +++ b/nyaapy/__init__.py @@ -0,0 +1,7 @@ +# Info about the module +__version__ = "0.6.3" +__author__ = "Juanjo Salvador" +__email__ = "juanjosalvador@netc.eu" +__url__ = "http://juanjosalvador.me" +__copyright__ = "2017 Juanjo Salvador" +__license__ = "MIT license" diff --git a/NyaaPy/nyaa.py b/nyaapy/nyaa.py similarity index 51% rename from NyaaPy/nyaa.py rename to nyaapy/nyaa.py index fba0a22..6735e87 100644 --- a/NyaaPy/nyaa.py +++ b/nyaapy/nyaa.py @@ -1,6 +1,6 @@ import requests -from NyaaPy import utils -from NyaaPy import torrent +from nyaapy import utils +from nyaapy import torrent class Nyaa: @@ -16,33 +16,49 @@ class Nyaa: r.raise_for_status() json_data = utils.parse_nyaa( - request_text=r.text, - limit=number_of_results + 1, - site=self.SITE + request_text=r.text, limit=number_of_results + 1, site=self.SITE ) - + return torrent.json_to_class(json_data) def search(self, keyword, **kwargs): base_url = self.URL - user = kwargs.get('user', None) - category = kwargs.get('category', 0) - subcategory = kwargs.get('subcategory', 0) - filters = kwargs.get('filters', 0) - page = kwargs.get('page', 0) - sorting = kwargs.get('sort', 'id') # Sorting by id = sorting by date, this is the default. - order = kwargs.get('order', 'desc') + user = kwargs.get("user", None) + category = kwargs.get("category", 0) + subcategory = kwargs.get("subcategory", 0) + filters = kwargs.get("filters", 0) + page = kwargs.get("page", 0) + sorting = kwargs.get( + "sort", "id" + ) # Sorting by id = sorting by date, this is the default. + order = kwargs.get("order", "desc") user_uri = f"user/{user}" if user else "" if page > 0: search_uri = "{}/{}?f={}&c={}_{}&q={}&p={}&s={}&o={}".format( - base_url, user_uri, filters, category, subcategory, keyword, - page, sorting, order) + base_url, + user_uri, + filters, + category, + subcategory, + keyword, + page, + sorting, + order, + ) else: search_uri = "{}/{}?f={}&c={}_{}&q={}&s={}&o={}".format( - base_url, user_uri, filters, category, subcategory, keyword, sorting, order) + base_url, + user_uri, + filters, + category, + subcategory, + keyword, + sorting, + order, + ) if not user: search_uri += "&page=rss" @@ -52,22 +68,18 @@ class Nyaa: if user: json_data = utils.parse_nyaa( - request_text=http_response.text, - limit=None, - site=self.SITE + request_text=http_response.text, limit=None, site=self.SITE ) else: json_data = utils.parse_nyaa_rss( - request_text=http_response.text, - limit=None, - site=self.SITE + request_text=http_response.text, limit=None, site=self.SITE ) # Convert JSON data to a class object return torrent.json_to_class(json_data) def get(self, view_id): - r = requests.get(f'{self.URL}/view/{view_id}') + r = requests.get(f"{self.URL}/view/{view_id}") r.raise_for_status() json_data = utils.parse_single(request_text=r.text, site=self.SITE) @@ -75,12 +87,8 @@ class Nyaa: return torrent.json_to_class(json_data) def get_user(self, username): - r = requests.get(f'{self.URL}/user/{username}') + r = requests.get(f"{self.URL}/user/{username}") r.raise_for_status() - json_data = utils.parse_nyaa( - request_text=r.text, - limit=None, - site=self.SITE - ) + json_data = utils.parse_nyaa(request_text=r.text, limit=None, site=self.SITE) return torrent.json_to_class(json_data) diff --git a/NyaaPy/sukebei.py b/nyaapy/sukebei.py similarity index 55% rename from NyaaPy/sukebei.py rename to nyaapy/sukebei.py index 55a657c..08a38b8 100644 --- a/NyaaPy/sukebei.py +++ b/nyaapy/sukebei.py @@ -1,5 +1,5 @@ import requests -from NyaaPy import utils +from nyaapy import utils class SukebeiNyaa: @@ -9,19 +9,23 @@ class SukebeiNyaa: def search(self, keyword, **kwargs): uri = self.SITE.value - category = kwargs.get('category', 0) - subcategory = kwargs.get('subcategory', 0) - filters = kwargs.get('filters', 0) - page = kwargs.get('page', 0) + category = kwargs.get("category", 0) + subcategory = kwargs.get("subcategory", 0) + filters = kwargs.get("filters", 0) + page = kwargs.get("page", 0) if page > 0: - r = requests.get("{}/?f={}&c={}_{}&q={}&p={}".format( - uri, filters, category, subcategory, - keyword, page)) + r = requests.get( + "{}/?f={}&c={}_{}&q={}&p={}".format( + uri, filters, category, subcategory, keyword, page + ) + ) else: - r = requests.get("{}/?f={}&c={}_{}&q={}".format( - uri, filters, category, subcategory, - keyword)) + r = requests.get( + "{}/?f={}&c={}_{}&q={}".format( + uri, filters, category, subcategory, keyword + ) + ) r.raise_for_status() return utils.parse_nyaa(r.text, limit=None, site=self.SITE) @@ -42,8 +46,4 @@ class SukebeiNyaa: r = requests.get(self.SITE.value) r.raise_for_status() - return utils.parse_nyaa( - r.text, - limit=number_of_results + 1, - site=self.SITE - ) + return utils.parse_nyaa(r.text, limit=number_of_results + 1, site=self.SITE) diff --git a/NyaaPy/torrent.py b/nyaapy/torrent.py similarity index 100% rename from NyaaPy/torrent.py rename to nyaapy/torrent.py diff --git a/NyaaPy/utils.py b/nyaapy/utils.py similarity index 62% rename from NyaaPy/utils.py rename to nyaapy/utils.py index dfe344d..6aa944c 100644 --- a/NyaaPy/utils.py +++ b/nyaapy/utils.py @@ -9,6 +9,7 @@ class TorrentSite(Enum): """ Contains torrent sites """ + NYAASI = "https://nyaa.si" SUKEBEINYAASI = "https://sukebei.nyaa.si" @@ -18,8 +19,8 @@ class TorrentSite(Enum): def nyaa_categories(b): - c = b.replace('?c=', '') - cats = c.split('_') + c = b.replace("?c=", "") + cats = c.split("_") cat = cats[0] sub_cat = cats[1] @@ -31,23 +32,17 @@ def nyaa_categories(b): "1": "Anime Music Video", "2": "English-translated", "3": "Non-English-translated", - "4": "Raw" - } - }, - "2": { - "name": "Audio", - "sub_cats": { - "1": "Lossless", - "2": "Lossy" - } + "4": "Raw", + }, }, + "2": {"name": "Audio", "sub_cats": {"1": "Lossless", "2": "Lossy"}}, "3": { "name": "Literature", "sub_cats": { "1": "English-translated", "2": "Non-English-translated", - "3": "Raw" - } + "3": "Raw", + }, }, "4": { "name": "Live Action", @@ -55,27 +50,17 @@ def nyaa_categories(b): "1": "English-translated", "2": "Idol/Promotional Video", "3": "Non-English-translated", - "4": "Raw" - } + "4": "Raw", + }, }, - "5": { - "name": "Pictures", - "sub_cats": { - "1": "Graphics", - "2": "Photos" - } - }, - "6": { - "name": "Software", - "sub_cats": { - "1": "Applications", - "2": "Games" - } - } + "5": {"name": "Pictures", "sub_cats": {"1": "Graphics", "2": "Photos"}}, + "6": {"name": "Software", "sub_cats": {"1": "Applications", "2": "Games"}}, } try: - category_name = f"{categories[cat]['name']} - {categories[cat]['sub_cats'][sub_cat]}" + category_name = ( + f"{categories[cat]['name']} - {categories[cat]['sub_cats'][sub_cat]}" + ) except KeyError: print("Unable to get Nyaa category name") return @@ -102,21 +87,26 @@ def parse_nyaa_rss(request_text, limit, site): try: is_remake = item.findtext("nyaa:remake", namespaces=item.nsmap) == "Yes" is_trusted = item.findtext("nyaa:trusted", namespaces=item.nsmap) == "Yes" - item_type = "remake" if is_remake else "trusted" if is_trusted else "default" + item_type = ( + "remake" if is_remake else "trusted" if is_trusted else "default" + ) torrent = { - 'id': item.findtext("guid").split("/")[-1], - 'category': category, - 'url': item.findtext("guid"), - 'name': item.findtext("title"), - 'download_url': item.findtext("link"), - 'magnet': magnet_builder(item.findtext("nyaa:infoHash", namespaces=item.nsmap), item.findtext("title")), - 'size': item.findtext("nyaa:size", namespaces=item.nsmap), - 'date': item.findtext("pubDate"), - 'seeders': item.findtext("nyaa:seeders", namespaces=item.nsmap), - 'leechers': item.findtext("nyaa:leechers", namespaces=item.nsmap), - 'completed_downloads': None, - 'type': item_type + "id": item.findtext("guid").split("/")[-1], + "category": category, + "url": item.findtext("guid"), + "name": item.findtext("title"), + "download_url": item.findtext("link"), + "magnet": magnet_builder( + item.findtext("nyaa:infoHash", namespaces=item.nsmap), + item.findtext("title"), + ), + "size": item.findtext("nyaa:size", namespaces=item.nsmap), + "date": item.findtext("pubDate"), + "seeders": item.findtext("nyaa:seeders", namespaces=item.nsmap), + "leechers": item.findtext("nyaa:leechers", namespaces=item.nsmap), + "completed_downloads": None, + "type": item_type, } torrents.append(torrent) except IndexError: @@ -141,7 +131,7 @@ def parse_nyaa(request_text, limit, site): for td in tr.xpath("./td"): for link in td.xpath("./a"): - href = link.attrib.get("href").split('/')[-1] + href = link.attrib.get("href").split("/")[-1] # Only caring about non-comment pages. if href[-9:] != "#comments": @@ -155,9 +145,9 @@ def parse_nyaa(request_text, limit, site): # Add type of torrent based on tr class. if tr.attrib.get("class") is not None: - if 'danger' in tr.attrib.get("class"): + if "danger" in tr.attrib.get("class"): block.append("remake") - elif 'success' in tr.attrib.get("class"): + elif "success" in tr.attrib.get("class"): block.append("trusted") else: block.append("default") @@ -175,18 +165,18 @@ def parse_nyaa(request_text, limit, site): # Create torrent object try: torrent = { - 'id': block[1], - 'category': category, - 'url': "{}/view/{}".format(uri, block[1]), - 'name': block[2], - 'download_url': "{}/download/{}".format(uri, block[3]), - 'magnet': block[4], - 'size': block[5], - 'date': block[6], - 'seeders': block[7], - 'leechers': block[8], - 'completed_downloads': block[9], - 'type': block[10] + "id": block[1], + "category": category, + "url": "{}/view/{}".format(uri, block[1]), + "name": block[2], + "download_url": "{}/download/{}".format(uri, block[3]), + "magnet": block[4], + "size": block[5], + "date": block[6], + "seeders": block[7], + "leechers": block[8], + "completed_downloads": block[9], + "type": block[10], } torrents.append(torrent) except IndexError: @@ -218,30 +208,29 @@ def parse_single(request_text, site): if el.rstrip(): torrent_files.append(el) - torrent['title'] = \ - tree.xpath("//h3[@class='panel-title']/text()")[0].strip() - torrent['category'] = data[0] - torrent['uploader'] = data[4] - torrent['uploader_profile'] = "{}/user/{}".format(uri, data[4]) - torrent['website'] = data[6] - torrent['size'] = data[8] - torrent['date'] = data[3] - torrent['seeders'] = data[5] - torrent['leechers'] = data[7] - torrent['completed'] = data[9] - torrent['hash'] = data[10] - torrent['files'] = torrent_files + torrent["title"] = tree.xpath("//h3[@class='panel-title']/text()")[0].strip() + torrent["category"] = data[0] + torrent["uploader"] = data[4] + torrent["uploader_profile"] = "{}/user/{}".format(uri, data[4]) + torrent["website"] = data[6] + torrent["size"] = data[8] + torrent["date"] = data[3] + torrent["seeders"] = data[5] + torrent["leechers"] = data[7] + torrent["completed"] = data[9] + torrent["hash"] = data[10] + torrent["files"] = torrent_files - torrent['description'] = "" + torrent["description"] = "" for s in tree.xpath("//div[@id='torrent-description']"): - torrent['description'] += s.text + torrent["description"] += s.text return torrent def sukebei_categories(b): - c = b.replace('?c=', '') - cats = c.split('_') + c = b.replace("?c=", "") + cats = c.split("_") cat = cats[0] subcat = cats[1] @@ -255,19 +244,18 @@ def sukebei_categories(b): "3": "Games", "4": "Manga", "5": "Pictures", - } + }, }, "2": { "name": "Real Life", - "subcats": { - "1": "Photobooks & Pictures", - "2": "Videos" - } - } + "subcats": {"1": "Photobooks & Pictures", "2": "Videos"}, + }, } try: - category_name = f"{categories[cat]['name']} - {categories[cat]['subcats'][subcat]}" + category_name = ( + f"{categories[cat]['name']} - {categories[cat]['subcats'][subcat]}" + ) except KeyError: print("Unable to get Sukebei category name") return @@ -284,10 +272,12 @@ def magnet_builder(info_hash, title): "udp://open.stealth.si:80/announce", "udp://tracker.opentrackr.org:1337/announce", "udp://exodus.desync.com:6969/announce", - "udp://tracker.torrent.eu.org:451/announce" + "udp://tracker.torrent.eu.org:451/announce", ] - magnet_link = f"magnet:?xt=urn:btih:{info_hash}&" + urlencode({"dn": title}, quote_via=urllib.parse.quote) + magnet_link = f"magnet:?xt=urn:btih:{info_hash}&" + urlencode( + {"dn": title}, quote_via=urllib.parse.quote + ) for tracker in known_trackers: magnet_link += f"&{urlencode({'tr': tracker})}" @@ -296,16 +286,29 @@ def magnet_builder(info_hash, title): # Pantsu Utils def query_builder(q, params): - available_params = ["category", "page", "limit", "userID", "fromID", - "status", "maxage", "toDate", "fromDate", - "dateType", "minSize", "maxSize", "sizeType", - "sort", "order", "lang"] + available_params = [ + "category", + "page", + "limit", + "userID", + "fromID", + "status", + "maxage", + "toDate", + "fromDate", + "dateType", + "minSize", + "maxSize", + "sizeType", + "sort", + "order", + "lang", + ] query = "?q={}".format(q.replace(" ", "+")) for param, value in params.items(): if param in available_params: - if (param != "category" and param != "status" and - param != "lang"): + if param != "category" and param != "status" and param != "lang": query += "&{}={}".format(param, value) elif param == "category": query += "&c={}_{}".format(value[0], value[1]) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..600263d --- /dev/null +++ b/poetry.lock @@ -0,0 +1,602 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "black" +version = "24.4.2" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"}, + {file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"}, + {file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"}, + {file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"}, + {file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"}, + {file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"}, + {file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"}, + {file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"}, + {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, + {file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"}, + {file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"}, + {file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"}, + {file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"}, + {file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"}, + {file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"}, + {file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"}, + {file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"}, + {file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"}, + {file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"}, + {file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"}, + {file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"}, + {file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "lxml" +version = "5.2.2" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:364d03207f3e603922d0d3932ef363d55bbf48e3647395765f9bfcbdf6d23632"}, + {file = "lxml-5.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:50127c186f191b8917ea2fb8b206fbebe87fd414a6084d15568c27d0a21d60db"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74e4f025ef3db1c6da4460dd27c118d8cd136d0391da4e387a15e48e5c975147"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981a06a3076997adf7c743dcd0d7a0415582661e2517c7d961493572e909aa1d"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aef5474d913d3b05e613906ba4090433c515e13ea49c837aca18bde190853dff"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e275ea572389e41e8b039ac076a46cb87ee6b8542df3fff26f5baab43713bca"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5b65529bb2f21ac7861a0e94fdbf5dc0daab41497d18223b46ee8515e5ad297"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bcc98f911f10278d1daf14b87d65325851a1d29153caaf146877ec37031d5f36"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_ppc64le.whl", hash = "sha256:b47633251727c8fe279f34025844b3b3a3e40cd1b198356d003aa146258d13a2"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_s390x.whl", hash = "sha256:fbc9d316552f9ef7bba39f4edfad4a734d3d6f93341232a9dddadec4f15d425f"}, + {file = "lxml-5.2.2-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:13e69be35391ce72712184f69000cda04fc89689429179bc4c0ae5f0b7a8c21b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3b6a30a9ab040b3f545b697cb3adbf3696c05a3a68aad172e3fd7ca73ab3c835"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a233bb68625a85126ac9f1fc66d24337d6e8a0f9207b688eec2e7c880f012ec0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:dfa7c241073d8f2b8e8dbc7803c434f57dbb83ae2a3d7892dd068d99e96efe2c"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a7aca7964ac4bb07680d5c9d63b9d7028cace3e2d43175cb50bba8c5ad33316"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae4073a60ab98529ab8a72ebf429f2a8cc612619a8c04e08bed27450d52103c0"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ffb2be176fed4457e445fe540617f0252a72a8bc56208fd65a690fdb1f57660b"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e290d79a4107d7d794634ce3e985b9ae4f920380a813717adf61804904dc4393"}, + {file = "lxml-5.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:96e85aa09274955bb6bd483eaf5b12abadade01010478154b0ec70284c1b1526"}, + {file = "lxml-5.2.2-cp310-cp310-win32.whl", hash = "sha256:f956196ef61369f1685d14dad80611488d8dc1ef00be57c0c5a03064005b0f30"}, + {file = "lxml-5.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:875a3f90d7eb5c5d77e529080d95140eacb3c6d13ad5b616ee8095447b1d22e7"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45f9494613160d0405682f9eee781c7e6d1bf45f819654eb249f8f46a2c22545"}, + {file = "lxml-5.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b0b3f2df149efb242cee2ffdeb6674b7f30d23c9a7af26595099afaf46ef4e88"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d28cb356f119a437cc58a13f8135ab8a4c8ece18159eb9194b0d269ec4e28083"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:657a972f46bbefdbba2d4f14413c0d079f9ae243bd68193cb5061b9732fa54c1"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b9ea10063efb77a965a8d5f4182806fbf59ed068b3c3fd6f30d2ac7bee734"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:07542787f86112d46d07d4f3c4e7c760282011b354d012dc4141cc12a68cef5f"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:303f540ad2dddd35b92415b74b900c749ec2010e703ab3bfd6660979d01fd4ed"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2eb2227ce1ff998faf0cd7fe85bbf086aa41dfc5af3b1d80867ecfe75fb68df3"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:1d8a701774dfc42a2f0b8ccdfe7dbc140500d1049e0632a611985d943fcf12df"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:56793b7a1a091a7c286b5f4aa1fe4ae5d1446fe742d00cdf2ffb1077865db10d"}, + {file = "lxml-5.2.2-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:eb00b549b13bd6d884c863554566095bf6fa9c3cecb2e7b399c4bc7904cb33b5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a2569a1f15ae6c8c64108a2cd2b4a858fc1e13d25846be0666fc144715e32ab"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:8cf85a6e40ff1f37fe0f25719aadf443686b1ac7652593dc53c7ef9b8492b115"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:d237ba6664b8e60fd90b8549a149a74fcc675272e0e95539a00522e4ca688b04"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0b3f5016e00ae7630a4b83d0868fca1e3d494c78a75b1c7252606a3a1c5fc2ad"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:23441e2b5339bc54dc949e9e675fa35efe858108404ef9aa92f0456929ef6fe8"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2fb0ba3e8566548d6c8e7dd82a8229ff47bd8fb8c2da237607ac8e5a1b8312e5"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:79d1fb9252e7e2cfe4de6e9a6610c7cbb99b9708e2c3e29057f487de5a9eaefa"}, + {file = "lxml-5.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6dcc3d17eac1df7859ae01202e9bb11ffa8c98949dcbeb1069c8b9a75917e01b"}, + {file = "lxml-5.2.2-cp311-cp311-win32.whl", hash = "sha256:4c30a2f83677876465f44c018830f608fa3c6a8a466eb223535035fbc16f3438"}, + {file = "lxml-5.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:49095a38eb333aaf44c06052fd2ec3b8f23e19747ca7ec6f6c954ffea6dbf7be"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7429e7faa1a60cad26ae4227f4dd0459efde239e494c7312624ce228e04f6391"}, + {file = "lxml-5.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:50ccb5d355961c0f12f6cf24b7187dbabd5433f29e15147a67995474f27d1776"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc911208b18842a3a57266d8e51fc3cfaccee90a5351b92079beed912a7914c2"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ce9e786753743159799fdf8e92a5da351158c4bfb6f2db0bf31e7892a1feb5"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ec87c44f619380878bd49ca109669c9f221d9ae6883a5bcb3616785fa8f94c97"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08ea0f606808354eb8f2dfaac095963cb25d9d28e27edcc375d7b30ab01abbf6"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75a9632f1d4f698b2e6e2e1ada40e71f369b15d69baddb8968dcc8e683839b18"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74da9f97daec6928567b48c90ea2c82a106b2d500f397eeb8941e47d30b1ca85"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:0969e92af09c5687d769731e3f39ed62427cc72176cebb54b7a9d52cc4fa3b73"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:9164361769b6ca7769079f4d426a41df6164879f7f3568be9086e15baca61466"}, + {file = "lxml-5.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d26a618ae1766279f2660aca0081b2220aca6bd1aa06b2cf73f07383faf48927"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab67ed772c584b7ef2379797bf14b82df9aa5f7438c5b9a09624dd834c1c1aaf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3d1e35572a56941b32c239774d7e9ad724074d37f90c7a7d499ab98761bd80cf"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:8268cbcd48c5375f46e000adb1390572c98879eb4f77910c6053d25cc3ac2c67"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e282aedd63c639c07c3857097fc0e236f984ceb4089a8b284da1c526491e3f3d"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfdc2bfe69e9adf0df4915949c22a25b39d175d599bf98e7ddf620a13678585"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4aefd911793b5d2d7a921233a54c90329bf3d4a6817dc465f12ffdfe4fc7b8fe"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8b8df03a9e995b6211dafa63b32f9d405881518ff1ddd775db4e7b98fb545e1c"}, + {file = "lxml-5.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f11ae142f3a322d44513de1018b50f474f8f736bc3cd91d969f464b5bfef8836"}, + {file = "lxml-5.2.2-cp312-cp312-win32.whl", hash = "sha256:16a8326e51fcdffc886294c1e70b11ddccec836516a343f9ed0f82aac043c24a"}, + {file = "lxml-5.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bbc4b80af581e18568ff07f6395c02114d05f4865c2812a1f02f2eaecf0bfd48"}, + {file = "lxml-5.2.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e3d9d13603410b72787579769469af730c38f2f25505573a5888a94b62b920f8"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38b67afb0a06b8575948641c1d6d68e41b83a3abeae2ca9eed2ac59892b36706"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c689d0d5381f56de7bd6966a4541bff6e08bf8d3871bbd89a0c6ab18aa699573"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_28_x86_64.whl", hash = "sha256:cf2a978c795b54c539f47964ec05e35c05bd045db5ca1e8366988c7f2fe6b3ce"}, + {file = "lxml-5.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:739e36ef7412b2bd940f75b278749106e6d025e40027c0b94a17ef7968d55d56"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d8bbcd21769594dbba9c37d3c819e2d5847656ca99c747ddb31ac1701d0c0ed9"}, + {file = "lxml-5.2.2-cp36-cp36m-musllinux_1_2_x86_64.whl", hash = "sha256:2304d3c93f2258ccf2cf7a6ba8c761d76ef84948d87bf9664e14d203da2cd264"}, + {file = "lxml-5.2.2-cp36-cp36m-win32.whl", hash = "sha256:02437fb7308386867c8b7b0e5bc4cd4b04548b1c5d089ffb8e7b31009b961dc3"}, + {file = "lxml-5.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:edcfa83e03370032a489430215c1e7783128808fd3e2e0a3225deee278585196"}, + {file = "lxml-5.2.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28bf95177400066596cdbcfc933312493799382879da504633d16cf60bba735b"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a745cc98d504d5bd2c19b10c79c61c7c3df9222629f1b6210c0368177589fb8"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b590b39ef90c6b22ec0be925b211298e810b4856909c8ca60d27ffbca6c12e6"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b336b0416828022bfd5a2e3083e7f5ba54b96242159f83c7e3eebaec752f1716"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:c2faf60c583af0d135e853c86ac2735ce178f0e338a3c7f9ae8f622fd2eb788c"}, + {file = "lxml-5.2.2-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:4bc6cb140a7a0ad1f7bc37e018d0ed690b7b6520ade518285dc3171f7a117905"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7ff762670cada8e05b32bf1e4dc50b140790909caa8303cfddc4d702b71ea184"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:57f0a0bbc9868e10ebe874e9f129d2917750adf008fe7b9c1598c0fbbfdde6a6"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:a6d2092797b388342c1bc932077ad232f914351932353e2e8706851c870bca1f"}, + {file = "lxml-5.2.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:60499fe961b21264e17a471ec296dcbf4365fbea611bf9e303ab69db7159ce61"}, + {file = "lxml-5.2.2-cp37-cp37m-win32.whl", hash = "sha256:d9b342c76003c6b9336a80efcc766748a333573abf9350f4094ee46b006ec18f"}, + {file = "lxml-5.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b16db2770517b8799c79aa80f4053cd6f8b716f21f8aca962725a9565ce3ee40"}, + {file = "lxml-5.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ed07b3062b055d7a7f9d6557a251cc655eed0b3152b76de619516621c56f5d3"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f60fdd125d85bf9c279ffb8e94c78c51b3b6a37711464e1f5f31078b45002421"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a7e24cb69ee5f32e003f50e016d5fde438010c1022c96738b04fc2423e61706"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23cfafd56887eaed93d07bc4547abd5e09d837a002b791e9767765492a75883f"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19b4e485cd07b7d83e3fe3b72132e7df70bfac22b14fe4bf7a23822c3a35bff5"}, + {file = "lxml-5.2.2-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:7ce7ad8abebe737ad6143d9d3bf94b88b93365ea30a5b81f6877ec9c0dee0a48"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e49b052b768bb74f58c7dda4e0bdf7b79d43a9204ca584ffe1fb48a6f3c84c66"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d14a0d029a4e176795cef99c056d58067c06195e0c7e2dbb293bf95c08f772a3"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:be49ad33819d7dcc28a309b86d4ed98e1a65f3075c6acd3cd4fe32103235222b"}, + {file = "lxml-5.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a6d17e0370d2516d5bb9062c7b4cb731cff921fc875644c3d751ad857ba9c5b1"}, + {file = "lxml-5.2.2-cp38-cp38-win32.whl", hash = "sha256:5b8c041b6265e08eac8a724b74b655404070b636a8dd6d7a13c3adc07882ef30"}, + {file = "lxml-5.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:f61efaf4bed1cc0860e567d2ecb2363974d414f7f1f124b1df368bbf183453a6"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb91819461b1b56d06fa4bcf86617fac795f6a99d12239fb0c68dbeba41a0a30"}, + {file = "lxml-5.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d4ed0c7cbecde7194cd3228c044e86bf73e30a23505af852857c09c24e77ec5d"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54401c77a63cc7d6dc4b4e173bb484f28a5607f3df71484709fe037c92d4f0ed"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:625e3ef310e7fa3a761d48ca7ea1f9d8718a32b1542e727d584d82f4453d5eeb"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:519895c99c815a1a24a926d5b60627ce5ea48e9f639a5cd328bda0515ea0f10c"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7079d5eb1c1315a858bbf180000757db8ad904a89476653232db835c3114001"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:343ab62e9ca78094f2306aefed67dcfad61c4683f87eee48ff2fd74902447726"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:cd9e78285da6c9ba2d5c769628f43ef66d96ac3085e59b10ad4f3707980710d3"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_ppc64le.whl", hash = "sha256:546cf886f6242dff9ec206331209db9c8e1643ae642dea5fdbecae2453cb50fd"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_s390x.whl", hash = "sha256:02f6a8eb6512fdc2fd4ca10a49c341c4e109aa6e9448cc4859af5b949622715a"}, + {file = "lxml-5.2.2-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:339ee4a4704bc724757cd5dd9dc8cf4d00980f5d3e6e06d5847c1b594ace68ab"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0a028b61a2e357ace98b1615fc03f76eb517cc028993964fe08ad514b1e8892d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f90e552ecbad426eab352e7b2933091f2be77115bb16f09f78404861c8322981"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d83e2d94b69bf31ead2fa45f0acdef0757fa0458a129734f59f67f3d2eb7ef32"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a02d3c48f9bb1e10c7788d92c0c7db6f2002d024ab6e74d6f45ae33e3d0288a3"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6d68ce8e7b2075390e8ac1e1d3a99e8b6372c694bbe612632606d1d546794207"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:453d037e09a5176d92ec0fd282e934ed26d806331a8b70ab431a81e2fbabf56d"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:3b019d4ee84b683342af793b56bb35034bd749e4cbdd3d33f7d1107790f8c472"}, + {file = "lxml-5.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cb3942960f0beb9f46e2a71a3aca220d1ca32feb5a398656be934320804c0df9"}, + {file = "lxml-5.2.2-cp39-cp39-win32.whl", hash = "sha256:ac6540c9fff6e3813d29d0403ee7a81897f1d8ecc09a8ff84d2eea70ede1cdbf"}, + {file = "lxml-5.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:610b5c77428a50269f38a534057444c249976433f40f53e3b47e68349cca1425"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b537bd04d7ccd7c6350cdaaaad911f6312cbd61e6e6045542f781c7f8b2e99d2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4820c02195d6dfb7b8508ff276752f6b2ff8b64ae5d13ebe02e7667e035000b9"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a09f6184f17a80897172863a655467da2b11151ec98ba8d7af89f17bf63dae"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:76acba4c66c47d27c8365e7c10b3d8016a7da83d3191d053a58382311a8bf4e1"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b128092c927eaf485928cec0c28f6b8bead277e28acf56800e972aa2c2abd7a2"}, + {file = "lxml-5.2.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ae791f6bd43305aade8c0e22f816b34f3b72b6c820477aab4d18473a37e8090b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a2f6a1bc2460e643785a2cde17293bd7a8f990884b822f7bca47bee0a82fc66b"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e8d351ff44c1638cb6e980623d517abd9f580d2e53bfcd18d8941c052a5a009"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bec4bd9133420c5c52d562469c754f27c5c9e36ee06abc169612c959bd7dbb07"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:55ce6b6d803890bd3cc89975fca9de1dff39729b43b73cb15ddd933b8bc20484"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ab6a358d1286498d80fe67bd3d69fcbc7d1359b45b41e74c4a26964ca99c3f8"}, + {file = "lxml-5.2.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:06668e39e1f3c065349c51ac27ae430719d7806c026fec462e5693b08b95696b"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9cd5323344d8ebb9fb5e96da5de5ad4ebab993bbf51674259dbe9d7a18049525"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89feb82ca055af0fe797a2323ec9043b26bc371365847dbe83c7fd2e2f181c34"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e481bba1e11ba585fb06db666bfc23dbe181dbafc7b25776156120bf12e0d5a6"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d6c6ea6a11ca0ff9cd0390b885984ed31157c168565702959c25e2191674a14"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3d98de734abee23e61f6b8c2e08a88453ada7d6486dc7cdc82922a03968928db"}, + {file = "lxml-5.2.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:69ab77a1373f1e7563e0fb5a29a8440367dec051da6c7405333699d07444f511"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:34e17913c431f5ae01d8658dbf792fdc457073dcdfbb31dc0cc6ab256e664a8d"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05f8757b03208c3f50097761be2dea0aba02e94f0dc7023ed73a7bb14ff11eb0"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a520b4f9974b0a0a6ed73c2154de57cdfd0c8800f4f15ab2b73238ffed0b36e"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5e097646944b66207023bc3c634827de858aebc226d5d4d6d16f0b77566ea182"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b5e4ef22ff25bfd4ede5f8fb30f7b24446345f3e79d9b7455aef2836437bc38a"}, + {file = "lxml-5.2.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ff69a9a0b4b17d78170c73abe2ab12084bdf1691550c5629ad1fe7849433f324"}, + {file = "lxml-5.2.2.tar.gz", hash = "sha256:bb2dc4898180bea79863d5487e5f9c7c34297414bad54bcd0f0852aee9cfdb87"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html-clean = ["lxml-html-clean"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.10)"] + +[[package]] +name = "lxml-stubs" +version = "0.5.1" +description = "Type annotations for the lxml package" +optional = false +python-versions = "*" +files = [ + {file = "lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d"}, + {file = "lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272"}, +] + +[package.extras] +test = ["coverage[toml] (>=7.2.5)", "mypy (>=1.2.0)", "pytest (>=7.3.0)", "pytest-mypy-plugins (>=1.10.1)"] + +[[package]] +name = "mypy" +version = "1.10.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.10.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e36f229acfe250dc660790840916eb49726c928e8ce10fbdf90715090fe4ae02"}, + {file = "mypy-1.10.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:51a46974340baaa4145363b9e051812a2446cf583dfaeba124af966fa44593f7"}, + {file = "mypy-1.10.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:901c89c2d67bba57aaaca91ccdb659aa3a312de67f23b9dfb059727cce2e2e0a"}, + {file = "mypy-1.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0cd62192a4a32b77ceb31272d9e74d23cd88c8060c34d1d3622db3267679a5d9"}, + {file = "mypy-1.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:a2cbc68cb9e943ac0814c13e2452d2046c2f2b23ff0278e26599224cf164e78d"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bd6f629b67bb43dc0d9211ee98b96d8dabc97b1ad38b9b25f5e4c4d7569a0c6a"}, + {file = "mypy-1.10.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a1bbb3a6f5ff319d2b9d40b4080d46cd639abe3516d5a62c070cf0114a457d84"}, + {file = "mypy-1.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8edd4e9bbbc9d7b79502eb9592cab808585516ae1bcc1446eb9122656c6066f"}, + {file = "mypy-1.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6166a88b15f1759f94a46fa474c7b1b05d134b1b61fca627dd7335454cc9aa6b"}, + {file = "mypy-1.10.1-cp311-cp311-win_amd64.whl", hash = "sha256:5bb9cd11c01c8606a9d0b83ffa91d0b236a0e91bc4126d9ba9ce62906ada868e"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d8681909f7b44d0b7b86e653ca152d6dff0eb5eb41694e163c6092124f8246d7"}, + {file = "mypy-1.10.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:378c03f53f10bbdd55ca94e46ec3ba255279706a6aacaecac52ad248f98205d3"}, + {file = "mypy-1.10.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bacf8f3a3d7d849f40ca6caea5c055122efe70e81480c8328ad29c55c69e93e"}, + {file = "mypy-1.10.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:701b5f71413f1e9855566a34d6e9d12624e9e0a8818a5704d74d6b0402e66c04"}, + {file = "mypy-1.10.1-cp312-cp312-win_amd64.whl", hash = "sha256:3c4c2992f6ea46ff7fce0072642cfb62af7a2484efe69017ed8b095f7b39ef31"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:604282c886497645ffb87b8f35a57ec773a4a2721161e709a4422c1636ddde5c"}, + {file = "mypy-1.10.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37fd87cab83f09842653f08de066ee68f1182b9b5282e4634cdb4b407266bade"}, + {file = "mypy-1.10.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8addf6313777dbb92e9564c5d32ec122bf2c6c39d683ea64de6a1fd98b90fe37"}, + {file = "mypy-1.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5cc3ca0a244eb9a5249c7c583ad9a7e881aa5d7b73c35652296ddcdb33b2b9c7"}, + {file = "mypy-1.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:1b3a2ffce52cc4dbaeee4df762f20a2905aa171ef157b82192f2e2f368eec05d"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe85ed6836165d52ae8b88f99527d3d1b2362e0cb90b005409b8bed90e9059b3"}, + {file = "mypy-1.10.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2ae450d60d7d020d67ab440c6e3fae375809988119817214440033f26ddf7bf"}, + {file = "mypy-1.10.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6be84c06e6abd72f960ba9a71561c14137a583093ffcf9bbfaf5e613d63fa531"}, + {file = "mypy-1.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2189ff1e39db399f08205e22a797383613ce1cb0cb3b13d8bcf0170e45b96cc3"}, + {file = "mypy-1.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:97a131ee36ac37ce9581f4220311247ab6cba896b4395b9c87af0675a13a755f"}, + {file = "mypy-1.10.1-py3-none-any.whl", hash = "sha256:71d8ac0b906354ebda8ef1673e5fde785936ac1f29ff6987c7483cfbd5a4235a"}, + {file = "mypy-1.10.1.tar.gz", hash = "sha256:1f8f492d7db9e3593ef42d4f115f04e556130f2819ad33ab84551403e97dd4c0"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +mypyc = ["setuptools (>=50)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "platformdirs" +version = "4.2.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] +type = ["mypy (>=1.8)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.2.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.2.2-py3-none-any.whl", hash = "sha256:c434598117762e2bd304e526244f67bf66bbd7b5d6cf22138be51ff661980343"}, + {file = "pytest-8.2.2.tar.gz", hash = "sha256:de4bb8104e201939ccdc688b27a89a7be2079b22e2bd2b07f806b6ba71117977"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20240712" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "types-requests-2.32.0.20240712.tar.gz", hash = "sha256:90c079ff05e549f6bf50e02e910210b98b8ff1ebdd18e19c873cd237737c1358"}, + {file = "types_requests-2.32.0.20240712-py3-none-any.whl", hash = "sha256:f754283e152c752e46e70942fa2a146b5bc70393522257bb85bd1ef7e019dcc3"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "bd09d4b9f6f3ae48c750ce8dbd1bacc8b75e265b4363f77bf095962f9d1ebeac" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..37f942e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[tool.poetry] +name = "nyaapy" +version = "0.6.3" +description = "Unofficial Python wrapper for NyaaPantsu API and Nyaa.si" +authors = ["Juanjo Salvador "] +license = "MIT" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.10" +requests = "^2.32.3" +lxml = "^5.2.2" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.2.2" +black = "^24.4.2" +mypy = "^1.10.1" +types-requests = "^2.32.0.20240712" +lxml-stubs = "^0.5.1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/setup.py b/setup.py index 5f782c3..0a6c413 100644 --- a/setup.py +++ b/setup.py @@ -1,25 +1,25 @@ -from setuptools import setup, find_packages +from setuptools import setup, find_packages # type: ignore from os import path currdir = path.abspath(path.dirname(__file__)) -with open(path.join(currdir, 'README.md'), encoding='utf-8') as f: +with open(path.join(currdir, "README.md"), encoding="utf-8") as f: long_desc = f.read() -setup(name='nyaapy', - version='0.6.3', - install_requires=[ - "requests", - "beautifulsoup4", - "lxml", - ], - url='https://github.com/juanjosalvador/nyaapy', - long_description=long_desc, - long_description_content_type='text/markdown', - download_url=('https://github.com/juanjosalvador/' - 'nyaapy/archive/0.6.2.tar.gz'), - license='MIT', - author='Juanjo Salvador', - author_email='juanjosalvador@netc.eu', - description='Allows you to make requests on Nyaa.si and nyaa.pantsu.cat', - packages=find_packages(exclude=['tests']), - zip_safe=False) +setup( + name="nyaapy", + version="0.6.3", + install_requires=[ + "requests", + "lxml", + ], + url="https://github.com/juanjosalvador/nyaapy", + long_description=long_desc, + long_description_content_type="text/markdown", + download_url=("https://github.com/juanjosalvador/" "nyaapy/archive/0.6.3.tar.gz"), + license="MIT", + author="Juanjo Salvador", + author_email="juanjosalvador@netc.eu", + description="Allows you to make requests on Nyaa.si and nyaa.pantsu.cat", + packages=find_packages(exclude=["tests"]), + zip_safe=False, +) diff --git a/tests/test.py b/tests/test.py index 10f67fd..b473619 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,4 +1,4 @@ -from NyaaPy.nyaa import Nyaa +from nyaapy.nyaa import Nyaa from pprint import pprint from datetime import datetime import json @@ -16,64 +16,59 @@ nyaa = Nyaa() dt_latest_torrents_begin = datetime.now() latest_torrents = nyaa.last_uploads(100) dt_latest_torrents_end = datetime.now() -with open("test_files/nyaa_latest_torrent_test.json", 'w') as f: +with open("test_files/nyaa_latest_torrent_test.json", "w") as f: for torrent in latest_torrents: try: # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode('utf-8')) + '\n') + f.write(str(torrent.name.encode("utf-8")) + "\n") except AttributeError: - f.write('No name found for this torrent') + f.write("No name found for this torrent") # Search some nasty stuff dt_search_begin = datetime.now() test_search = nyaa.search("kimi no na wa") dt_search_end = datetime.now() -with open("test_files/nyaa_search_test.json", 'w') as f: +with open("test_files/nyaa_search_test.json", "w") as f: for torrent in test_search: try: # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode('utf-8')) + '\n') + f.write(str(torrent.name.encode("utf-8")) + "\n") except AttributeError: - f.write('No name found for this torrent') + f.write("No name found for this torrent") # Get first torrent from found torrents dt_single_torrent_begin = datetime.now() single_torrent = test_search[0] dt_single_torrent_end = datetime.now() -with open("test_files/nyaa_single_torrent_test.json", 'w') as f: +with open("test_files/nyaa_single_torrent_test.json", "w") as f: try: # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode('utf-8')) + '\n') + f.write(str(torrent.name.encode("utf-8")) + "\n") except AttributeError: - f.write('No name found for this torrent') + f.write("No name found for this torrent") dt_user_begin = datetime.now() user_torrents = nyaa.get_user("HorribleSubs") dt_user_end = datetime.now() -with open("test_files/nyaa_single_user_test.json", 'w') as f: +with open("test_files/nyaa_single_user_test.json", "w") as f: for torrent in user_torrents: try: # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode('utf-8')) + '\n') + f.write(str(torrent.name.encode("utf-8")) + "\n") except AttributeError: - f.write('No name found for this torrent') + f.write("No name found for this torrent") print( "Latest torrents time:", (dt_latest_torrents_end - dt_latest_torrents_begin).microseconds / 1000, - "msec") + "msec", +) print( - "Test search time:", - (dt_search_end - dt_search_begin).microseconds / 1000, - "msec" + "Test search time:", (dt_search_end - dt_search_begin).microseconds / 1000, "msec" ) print( "Single torrent time:", (dt_single_torrent_end - dt_single_torrent_begin).microseconds / 1000, - "msec" -) -print( - "Single user time:", - (dt_user_end - dt_user_begin).microseconds / 1000, - "msec" + "msec", ) +print("Single user time:", (dt_user_end - dt_user_begin).microseconds / 1000, "msec") diff --git a/tests/test_pantsu.py b/tests/test_pantsu.py index f77e593..fdaf4e8 100644 --- a/tests/test_pantsu.py +++ b/tests/test_pantsu.py @@ -3,4 +3,5 @@ Regular data single_torrent parser not working from other Nyaa alternatives Needs some work """ + print("TODO") diff --git a/tests/test_sukebei.py b/tests/test_sukebei.py index a6ef1cb..add9179 100644 --- a/tests/test_sukebei.py +++ b/tests/test_sukebei.py @@ -1,4 +1,4 @@ -from NyaaPy.sukebei import SukebeiNyaa +from nyaapy.sukebei import SukebeiNyaa from datetime import datetime import json import os @@ -14,45 +14,40 @@ nyaa = SukebeiNyaa() dt_latest_torrents_begin = datetime.now() latest_torrents = nyaa.last_uploads(100) dt_latest_torrents_end = datetime.now() -with open("test_files/sukebei_latest_torrent_test.json", 'w') as f: +with open("test_files/sukebei_latest_torrent_test.json", "w") as f: json.dump(latest_torrents, f) # Search some nasty stuff dt_search_begin = datetime.now() test_search = nyaa.search("G Senjou no maou") dt_search_end = datetime.now() -with open("test_files/sukebei_search_test.json", 'w') as f: +with open("test_files/sukebei_search_test.json", "w") as f: json.dump(test_search, f) # Get first torrent from found torrents dt_single_torrent_begin = datetime.now() single_torrent = nyaa.get(test_search[0]["id"]) dt_single_torrent_end = datetime.now() -with open("test_files/sukebei_single_torrent_test.json", 'w') as f: +with open("test_files/sukebei_single_torrent_test.json", "w") as f: json.dump(single_torrent, f) dt_user_begin = datetime.now() user_torrents = nyaa.get_user("RUNBKK") dt_user_end = datetime.now() -with open("test_files/sukebei_single_user_test.json", 'w') as f: +with open("test_files/sukebei_single_user_test.json", "w") as f: json.dump(user_torrents, f) print( "Latest torrents time:", (dt_latest_torrents_end - dt_latest_torrents_begin).microseconds / 1000, - "msec") + "msec", +) print( - "Test search time:", - (dt_search_end - dt_search_begin).microseconds / 1000, - "msec" + "Test search time:", (dt_search_end - dt_search_begin).microseconds / 1000, "msec" ) print( "Single torrent time:", (dt_single_torrent_end - dt_single_torrent_begin).microseconds / 1000, - "msec" -) -print( - "Single user time:", - (dt_user_end - dt_user_begin).microseconds / 1000, - "msec" + "msec", ) +print("Single user time:", (dt_user_end - dt_user_begin).microseconds / 1000, "msec") From 2c3d3ea58ee4c6146c5eeb5fd9775c4d670d8223 Mon Sep 17 00:00:00 2001 From: Juanjo Salvador Date: Thu, 18 Jul 2024 09:45:02 +0000 Subject: [PATCH 2/4] feat(nyaasi): add static classes instead of object-based ones --- nyaapy/{nyaa.py => anime_site.py} | 39 +- nyaapy/magnet.py | 22 ++ nyaapy/nyaasi/__init__.py | 0 nyaapy/nyaasi/nyaa.py | 7 + nyaapy/nyaasi/sukebei.py | 6 + nyaapy/{utils.py => parser.py} | 570 +++++++++++++----------------- nyaapy/sukebei.py | 49 --- nyaapy/torrent.py | 14 +- poetry.lock | 2 +- tests/integration/test_nyaasi.py | 30 ++ tests/test.py | 74 ---- tests/test_pantsu.py | 7 - tests/test_sukebei.py | 53 --- 13 files changed, 346 insertions(+), 527 deletions(-) rename nyaapy/{nyaa.py => anime_site.py} (68%) create mode 100644 nyaapy/magnet.py create mode 100644 nyaapy/nyaasi/__init__.py create mode 100644 nyaapy/nyaasi/nyaa.py create mode 100644 nyaapy/nyaasi/sukebei.py rename nyaapy/{utils.py => parser.py} (74%) delete mode 100644 nyaapy/sukebei.py create mode 100644 tests/integration/test_nyaasi.py delete mode 100644 tests/test.py delete mode 100644 tests/test_pantsu.py delete mode 100644 tests/test_sukebei.py diff --git a/nyaapy/nyaa.py b/nyaapy/anime_site.py similarity index 68% rename from nyaapy/nyaa.py rename to nyaapy/anime_site.py index 6735e87..e94acbd 100644 --- a/nyaapy/nyaa.py +++ b/nyaapy/anime_site.py @@ -1,27 +1,26 @@ import requests -from nyaapy import utils from nyaapy import torrent +from nyaapy.parser import parse_nyaa, parse_single, parse_nyaa_rss +class AnimeTorrentSite: + SITE = torrent.TorrentSite.NYAASI + URL = "https://nyaa.si" -class Nyaa: - - def __init__(self): - self.SITE = utils.TorrentSite.NYAASI - self.URL = "https://nyaa.si" - - def last_uploads(self, number_of_results): + @classmethod + def last_uploads(self, number_of_results: int): r = requests.get(self.URL) # If anything up with nyaa servers let the user know. r.raise_for_status() - json_data = utils.parse_nyaa( - request_text=r.text, limit=number_of_results + 1, site=self.SITE + json_data = parse_nyaa( + request_text=r.text, limit=number_of_results, site=self.SITE ) return torrent.json_to_class(json_data) - def search(self, keyword, **kwargs): + @classmethod + def search(self, keyword: str, **kwargs): base_url = self.URL user = kwargs.get("user", None) @@ -67,28 +66,30 @@ class Nyaa: http_response.raise_for_status() if user: - json_data = utils.parse_nyaa( - request_text=http_response.text, limit=None, site=self.SITE + json_data = parse_nyaa( + request_text=http_response.content, limit=None, site=self.SITE ) else: - json_data = utils.parse_nyaa_rss( - request_text=http_response.text, limit=None, site=self.SITE + json_data = parse_nyaa_rss( + request_text=http_response.content, limit=None, site=self.SITE ) # Convert JSON data to a class object return torrent.json_to_class(json_data) - def get(self, view_id): + @classmethod + def get(self, view_id: int): r = requests.get(f"{self.URL}/view/{view_id}") r.raise_for_status() - json_data = utils.parse_single(request_text=r.text, site=self.SITE) + json_data = parse_single(request_text=r.content, site=self.SITE) return torrent.json_to_class(json_data) - def get_user(self, username): + @classmethod + def get_from_user(self, username): r = requests.get(f"{self.URL}/user/{username}") r.raise_for_status() - json_data = utils.parse_nyaa(request_text=r.text, limit=None, site=self.SITE) + json_data = parse_nyaa(request_text=r.content, limit=None, site=self.SITE) return torrent.json_to_class(json_data) diff --git a/nyaapy/magnet.py b/nyaapy/magnet.py new file mode 100644 index 0000000..f89849b --- /dev/null +++ b/nyaapy/magnet.py @@ -0,0 +1,22 @@ +import urllib +from urllib.parse import urlencode + +def magnet_builder(info_hash, title): + """ + Generates a magnet link using the info_hash and title of a given file. + """ + known_trackers = [ + "http://nyaa.tracker.wf:7777/announce", + "udp://open.stealth.si:80/announce", + "udp://tracker.opentrackr.org:1337/announce", + "udp://exodus.desync.com:6969/announce", + "udp://tracker.torrent.eu.org:451/announce", + ] + + magnet_link = f"magnet:?xt=urn:btih:{info_hash}&" + urlencode( + {"dn": title}, quote_via=urllib.parse.quote + ) + for tracker in known_trackers: + magnet_link += f"&{urlencode({'tr': tracker})}" + + return magnet_link \ No newline at end of file diff --git a/nyaapy/nyaasi/__init__.py b/nyaapy/nyaasi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nyaapy/nyaasi/nyaa.py b/nyaapy/nyaasi/nyaa.py new file mode 100644 index 0000000..76c24ec --- /dev/null +++ b/nyaapy/nyaasi/nyaa.py @@ -0,0 +1,7 @@ +from nyaapy.anime_site import AnimeTorrentSite +from nyaapy.torrent import TorrentSite + + +class Nyaa(AnimeTorrentSite): + SITE = TorrentSite.NYAASI + URL = "https://nyaa.si" \ No newline at end of file diff --git a/nyaapy/nyaasi/sukebei.py b/nyaapy/nyaasi/sukebei.py new file mode 100644 index 0000000..7f63b77 --- /dev/null +++ b/nyaapy/nyaasi/sukebei.py @@ -0,0 +1,6 @@ +from nyaapy.anime_site import AnimeTorrentSite +from nyaapy.torrent import TorrentSite + +class SukebeiNyaa(AnimeTorrentSite): + SITE = TorrentSite.SUKEBEINYAASI + URL = "https://sukebei.nyaa.si" \ No newline at end of file diff --git a/nyaapy/utils.py b/nyaapy/parser.py similarity index 74% rename from nyaapy/utils.py rename to nyaapy/parser.py index 6aa944c..93ddb7f 100644 --- a/nyaapy/utils.py +++ b/nyaapy/parser.py @@ -1,323 +1,247 @@ -import urllib -from enum import Enum -from urllib.parse import urlencode - -from lxml import etree - - -class TorrentSite(Enum): - """ - Contains torrent sites - """ - - NYAASI = "https://nyaa.si" - SUKEBEINYAASI = "https://sukebei.nyaa.si" - - # * nyaa.pantsu.cat redirects to nyaa.net - NYAANET = "https://nyaa.net" - SUKEBEINYAANET = "https://sukebei.nyaa.net" - - -def nyaa_categories(b): - c = b.replace("?c=", "") - cats = c.split("_") - - cat = cats[0] - sub_cat = cats[1] - - categories = { - "1": { - "name": "Anime", - "sub_cats": { - "1": "Anime Music Video", - "2": "English-translated", - "3": "Non-English-translated", - "4": "Raw", - }, - }, - "2": {"name": "Audio", "sub_cats": {"1": "Lossless", "2": "Lossy"}}, - "3": { - "name": "Literature", - "sub_cats": { - "1": "English-translated", - "2": "Non-English-translated", - "3": "Raw", - }, - }, - "4": { - "name": "Live Action", - "sub_cats": { - "1": "English-translated", - "2": "Idol/Promotional Video", - "3": "Non-English-translated", - "4": "Raw", - }, - }, - "5": {"name": "Pictures", "sub_cats": {"1": "Graphics", "2": "Photos"}}, - "6": {"name": "Software", "sub_cats": {"1": "Applications", "2": "Games"}}, - } - - try: - category_name = ( - f"{categories[cat]['name']} - {categories[cat]['sub_cats'][sub_cat]}" - ) - except KeyError: - print("Unable to get Nyaa category name") - return - - return category_name - - -def parse_nyaa_rss(request_text, limit, site): - """ - Extracts torrent information from a given rss response. - """ - root = etree.fromstring(request_text) - torrents = [] - - for item in root.xpath("channel/item")[:limit]: - # Decide category. - if site in [TorrentSite.NYAASI, TorrentSite.NYAANET]: - category = item.findtext("nyaa:categoryId", namespaces=item.nsmap) - elif site in [TorrentSite.SUKEBEINYAASI, TorrentSite.SUKEBEINYAANET]: - category = item.findtext("nyaa:categoryId", namespaces=item.nsmap) - else: - raise ValueError("Unknown TorrentSite received!") - - try: - is_remake = item.findtext("nyaa:remake", namespaces=item.nsmap) == "Yes" - is_trusted = item.findtext("nyaa:trusted", namespaces=item.nsmap) == "Yes" - item_type = ( - "remake" if is_remake else "trusted" if is_trusted else "default" - ) - - torrent = { - "id": item.findtext("guid").split("/")[-1], - "category": category, - "url": item.findtext("guid"), - "name": item.findtext("title"), - "download_url": item.findtext("link"), - "magnet": magnet_builder( - item.findtext("nyaa:infoHash", namespaces=item.nsmap), - item.findtext("title"), - ), - "size": item.findtext("nyaa:size", namespaces=item.nsmap), - "date": item.findtext("pubDate"), - "seeders": item.findtext("nyaa:seeders", namespaces=item.nsmap), - "leechers": item.findtext("nyaa:leechers", namespaces=item.nsmap), - "completed_downloads": None, - "type": item_type, - } - torrents.append(torrent) - except IndexError: - pass - - return torrents - - -def parse_nyaa(request_text, limit, site): - parser = etree.HTMLParser() - tree = etree.fromstring(request_text, parser) - - # Put proper domain here. - uri = site.value - - torrents = [] - - # Going through table rows - for tr in tree.xpath("//tbody//tr")[:limit]: - block = [] - - for td in tr.xpath("./td"): - for link in td.xpath("./a"): - - href = link.attrib.get("href").split("/")[-1] - - # Only caring about non-comment pages. - if href[-9:] != "#comments": - block.append(href) - - if link.text and link.text.strip(): - block.append(link.text.strip()) - - if td.text is not None and td.text.strip(): - block.append(td.text.strip()) - - # Add type of torrent based on tr class. - if tr.attrib.get("class") is not None: - if "danger" in tr.attrib.get("class"): - block.append("remake") - elif "success" in tr.attrib.get("class"): - block.append("trusted") - else: - block.append("default") - else: - block.append("default") - - # Decide category. - if site in [TorrentSite.NYAASI, TorrentSite.NYAANET]: - category = nyaa_categories(block[0]) - elif site in [TorrentSite.SUKEBEINYAASI, TorrentSite.SUKEBEINYAANET]: - category = sukebei_categories(block[0]) - else: - raise ValueError("Unknown TorrentSite received!") - - # Create torrent object - try: - torrent = { - "id": block[1], - "category": category, - "url": "{}/view/{}".format(uri, block[1]), - "name": block[2], - "download_url": "{}/download/{}".format(uri, block[3]), - "magnet": block[4], - "size": block[5], - "date": block[6], - "seeders": block[7], - "leechers": block[8], - "completed_downloads": block[9], - "type": block[10], - } - torrents.append(torrent) - except IndexError: - pass - return torrents - - -def parse_single(request_text, site): - parser = etree.HTMLParser() - tree = etree.fromstring(request_text, parser) - - # Put proper domain here. - uri = site.value - - torrent = {} - data = [] - torrent_files = [] - - # Find basic uploader info & torrent stats - for row in tree.xpath("//div[@class='row']"): - for div_text in row.xpath("./div[@class='col-md-5']//text()"): - d = div_text.strip() - if d: - data.append(d) - - # Find files, we need only text of the li element(s). - # Sorry about Pycodestyle aka PEP8 (E501) error - for el in tree.xpath("//div[contains(@class, 'torrent-file-list')]//li/text()"): - if el.rstrip(): - torrent_files.append(el) - - torrent["title"] = tree.xpath("//h3[@class='panel-title']/text()")[0].strip() - torrent["category"] = data[0] - torrent["uploader"] = data[4] - torrent["uploader_profile"] = "{}/user/{}".format(uri, data[4]) - torrent["website"] = data[6] - torrent["size"] = data[8] - torrent["date"] = data[3] - torrent["seeders"] = data[5] - torrent["leechers"] = data[7] - torrent["completed"] = data[9] - torrent["hash"] = data[10] - torrent["files"] = torrent_files - - torrent["description"] = "" - for s in tree.xpath("//div[@id='torrent-description']"): - torrent["description"] += s.text - - return torrent - - -def sukebei_categories(b): - c = b.replace("?c=", "") - cats = c.split("_") - - cat = cats[0] - subcat = cats[1] - - categories = { - "1": { - "name": "Art", - "subcats": { - "1": "Anime", - "2": "Doujinshi", - "3": "Games", - "4": "Manga", - "5": "Pictures", - }, - }, - "2": { - "name": "Real Life", - "subcats": {"1": "Photobooks & Pictures", "2": "Videos"}, - }, - } - - try: - category_name = ( - f"{categories[cat]['name']} - {categories[cat]['subcats'][subcat]}" - ) - except KeyError: - print("Unable to get Sukebei category name") - return - - return category_name - - -def magnet_builder(info_hash, title): - """ - Generates a magnet link using the info_hash and title of a given file. - """ - known_trackers = [ - "http://nyaa.tracker.wf:7777/announce", - "udp://open.stealth.si:80/announce", - "udp://tracker.opentrackr.org:1337/announce", - "udp://exodus.desync.com:6969/announce", - "udp://tracker.torrent.eu.org:451/announce", - ] - - magnet_link = f"magnet:?xt=urn:btih:{info_hash}&" + urlencode( - {"dn": title}, quote_via=urllib.parse.quote - ) - for tracker in known_trackers: - magnet_link += f"&{urlencode({'tr': tracker})}" - - return magnet_link - - -# Pantsu Utils -def query_builder(q, params): - available_params = [ - "category", - "page", - "limit", - "userID", - "fromID", - "status", - "maxage", - "toDate", - "fromDate", - "dateType", - "minSize", - "maxSize", - "sizeType", - "sort", - "order", - "lang", - ] - query = "?q={}".format(q.replace(" ", "+")) - - for param, value in params.items(): - if param in available_params: - if param != "category" and param != "status" and param != "lang": - query += "&{}={}".format(param, value) - elif param == "category": - query += "&c={}_{}".format(value[0], value[1]) - - elif param == "status": - query += "&s={}".format(value) - - elif param == "lang": - for lang in value: - query += "&lang={}".format(lang) - - return query +from lxml import etree +from nyaapy.magnet import magnet_builder +from nyaapy.torrent import TorrentSite + +def nyaa_categories(b): + c = b.replace("?c=", "") + cats = c.split("_") + + cat = cats[0] + sub_cat = cats[1] + + categories = { + "1": { + "name": "Anime", + "sub_cats": { + "1": "Anime Music Video", + "2": "English-translated", + "3": "Non-English-translated", + "4": "Raw", + }, + }, + "2": {"name": "Audio", "sub_cats": {"1": "Lossless", "2": "Lossy"}}, + "3": { + "name": "Literature", + "sub_cats": { + "1": "English-translated", + "2": "Non-English-translated", + "3": "Raw", + }, + }, + "4": { + "name": "Live Action", + "sub_cats": { + "1": "English-translated", + "2": "Idol/Promotional Video", + "3": "Non-English-translated", + "4": "Raw", + }, + }, + "5": {"name": "Pictures", "sub_cats": {"1": "Graphics", "2": "Photos"}}, + "6": {"name": "Software", "sub_cats": {"1": "Applications", "2": "Games"}}, + } + + try: + category_name = ( + f"{categories[cat]['name']} - {categories[cat]['sub_cats'][sub_cat]}" + ) + except KeyError: + print("Unable to get Nyaa category name") + return + + return category_name + + +def parse_nyaa_rss(request_text, limit, site): + """ + Extracts torrent information from a given rss response. + """ + root = etree.fromstring(request_text) + torrents = [] + + for item in root.xpath("channel/item")[:limit]: + # Decide category. + if site in [TorrentSite.NYAASI, TorrentSite.NYAALAND]: + category = item.findtext("nyaa:categoryId", namespaces=item.nsmap) + elif site in [TorrentSite.SUKEBEINYAASI, TorrentSite.SUKEBEINYAALAND]: + category = item.findtext("nyaa:categoryId", namespaces=item.nsmap) + else: + raise ValueError("Unknown TorrentSite received!") + + try: + is_remake = item.findtext("nyaa:remake", namespaces=item.nsmap) == "Yes" + is_trusted = item.findtext("nyaa:trusted", namespaces=item.nsmap) == "Yes" + item_type = ( + "remake" if is_remake else "trusted" if is_trusted else "default" + ) + + torrent = { + "id": item.findtext("guid").split("/")[-1], + "category": category, + "url": item.findtext("guid"), + "name": item.findtext("title"), + "download_url": item.findtext("link"), + "magnet": magnet_builder( + item.findtext("nyaa:infoHash", namespaces=item.nsmap), + item.findtext("title"), + ), + "size": item.findtext("nyaa:size", namespaces=item.nsmap), + "date": item.findtext("pubDate"), + "seeders": item.findtext("nyaa:seeders", namespaces=item.nsmap), + "leechers": item.findtext("nyaa:leechers", namespaces=item.nsmap), + "completed_downloads": None, + "type": item_type, + } + torrents.append(torrent) + except IndexError: + pass + + return torrents + + +def parse_nyaa(request_text, limit, site): + parser = etree.HTMLParser() + tree = etree.fromstring(request_text, parser) + + # Put proper domain here. + uri = site.value + + torrents = [] + + # Going through table rows + for tr in tree.xpath("//tbody//tr")[:limit]: + block = [] + + for td in tr.xpath("./td"): + for link in td.xpath("./a"): + + href = link.attrib.get("href").split("/")[-1] + + # Only caring about non-comment pages. + if href[-9:] != "#comments": + block.append(href) + + if link.text and link.text.strip(): + block.append(link.text.strip()) + + if td.text is not None and td.text.strip(): + block.append(td.text.strip()) + + # Add type of torrent based on tr class. + if tr.attrib.get("class") is not None: + if "danger" in tr.attrib.get("class"): + block.append("remake") + elif "success" in tr.attrib.get("class"): + block.append("trusted") + else: + block.append("default") + else: + block.append("default") + + # Decide category. + if site in [TorrentSite.NYAASI, TorrentSite.NYAALAND]: + category = nyaa_categories(block[0]) + elif site is TorrentSite.SUKEBEINYAASI: + category = sukebei_categories(block[0]) + else: + raise ValueError("Unknown TorrentSite received!") + + # Create torrent object + try: + torrent = { + "id": block[1], + "category": category, + "url": "{}/view/{}".format(uri, block[1]), + "name": block[2], + "download_url": "{}/download/{}".format(uri, block[3]), + "magnet": block[4], + "size": block[5], + "date": block[6], + "seeders": block[7], + "leechers": block[8], + "completed_downloads": block[9], + "type": block[10], + } + torrents.append(torrent) + except IndexError: + pass + return torrents + + +def parse_single(request_text, site): + parser = etree.HTMLParser() + tree = etree.fromstring(request_text, parser) + + # Put proper domain here. + uri = site.value + + torrent = {} + data = [] + torrent_files = [] + + # Find basic uploader info & torrent stats + for row in tree.xpath("//div[@class='row']"): + for div_text in row.xpath("./div[@class='col-md-5']//text()"): + d = div_text.strip() + if d: + data.append(d) + + # Find files, we need only text of the li element(s). + # Sorry about Pycodestyle aka PEP8 (E501) error + for el in tree.xpath("//div[contains(@class, 'torrent-file-list')]//li/text()"): + if el.rstrip(): + torrent_files.append(el) + + torrent["title"] = tree.xpath("//h3[@class='panel-title']/text()")[0].strip() + torrent["category"] = data[0] + torrent["uploader"] = data[4] + torrent["uploader_profile"] = "{}/user/{}".format(uri, data[4]) + torrent["website"] = data[6] + torrent["size"] = data[8] + torrent["date"] = data[3] + torrent["seeders"] = data[5] + torrent["leechers"] = data[7] + torrent["completed"] = data[9] + torrent["hash"] = data[10] + torrent["files"] = torrent_files + + torrent["description"] = "" + for s in tree.xpath("//div[@id='torrent-description']"): + torrent["description"] += s.text + + return torrent + + +def sukebei_categories(b): + c = b.replace("?c=", "") + cats = c.split("_") + + cat = cats[0] + subcat = cats[1] + + categories = { + "1": { + "name": "Art", + "subcats": { + "1": "Anime", + "2": "Doujinshi", + "3": "Games", + "4": "Manga", + "5": "Pictures", + }, + }, + "2": { + "name": "Real Life", + "subcats": {"1": "Photobooks & Pictures", "2": "Videos"}, + }, + } + + try: + category_name = ( + f"{categories[cat]['name']} - {categories[cat]['subcats'][subcat]}" + ) + except KeyError: + print("Unable to get Sukebei category name") + return + + return category_name diff --git a/nyaapy/sukebei.py b/nyaapy/sukebei.py deleted file mode 100644 index 08a38b8..0000000 --- a/nyaapy/sukebei.py +++ /dev/null @@ -1,49 +0,0 @@ -import requests -from nyaapy import utils - - -class SukebeiNyaa: - - def __init__(self): - self.SITE = utils.TorrentSite.SUKEBEINYAASI - - def search(self, keyword, **kwargs): - uri = self.SITE.value - category = kwargs.get("category", 0) - subcategory = kwargs.get("subcategory", 0) - filters = kwargs.get("filters", 0) - page = kwargs.get("page", 0) - - if page > 0: - r = requests.get( - "{}/?f={}&c={}_{}&q={}&p={}".format( - uri, filters, category, subcategory, keyword, page - ) - ) - else: - r = requests.get( - "{}/?f={}&c={}_{}&q={}".format( - uri, filters, category, subcategory, keyword - ) - ) - - r.raise_for_status() - return utils.parse_nyaa(r.text, limit=None, site=self.SITE) - - def get(self, id): - r = requests.get("{}/view/{}".format(self.SITE.value, id)) - r.raise_for_status() - - return utils.parse_single(r.text, self.SITE) - - def get_user(self, username): - r = requests.get("{}/user/{}".format(self.SITE.value, username)) - r.raise_for_status() - - return utils.parse_nyaa(r.text, limit=None, site=self.SITE) - - def last_uploads(self, number_of_results): - r = requests.get(self.SITE.value) - r.raise_for_status() - - return utils.parse_nyaa(r.text, limit=number_of_results + 1, site=self.SITE) diff --git a/nyaapy/torrent.py b/nyaapy/torrent.py index 8269c05..a5af209 100644 --- a/nyaapy/torrent.py +++ b/nyaapy/torrent.py @@ -1,3 +1,6 @@ +from enum import Enum + + def json_to_class(data): # We check if the data passed is a list or not if isinstance(data, list): @@ -9,9 +12,18 @@ def json_to_class(data): else: return Torrent(data) - # This deals with converting the dict to an object class Torrent(object): def __init__(self, my_dict): for key in my_dict: setattr(self, key, my_dict[key]) + +class TorrentSite(Enum): + """ + Contains torrent sites + """ + + NYAASI = "https://nyaa.si" + SUKEBEINYAASI = "https://sukebei.nyaa.si" + + NYAALAND = "https://nyaa.land" \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 600263d..d0ec109 100644 --- a/poetry.lock +++ b/poetry.lock @@ -599,4 +599,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "bd09d4b9f6f3ae48c750ce8dbd1bacc8b75e265b4363f77bf095962f9d1ebeac" +content-hash = "cb48b1a114a5cd1aa44d635f91c49ce640137746939a4feadb5615f8cfc7cf8b" diff --git a/tests/integration/test_nyaasi.py b/tests/integration/test_nyaasi.py new file mode 100644 index 0000000..e05b2ff --- /dev/null +++ b/tests/integration/test_nyaasi.py @@ -0,0 +1,30 @@ +from nyaapy.nyaasi.nyaa import Nyaa +from nyaapy.torrent import Torrent + +def test_nyaa_last_uploads(): + request = Nyaa.last_uploads(number_of_results=10) + torrent = request[0] + + assert isinstance(torrent, Torrent) == True + assert len(request) == 10 + + +def test_nyaa_search(): + request = Nyaa.search(keyword="koe no katachi") + torrent = request[0] + + assert isinstance(torrent, Torrent) == True + + +def test_nyaa_get_single(): + request = Nyaa.get(view_id='1847113') + + assert isinstance(request, Torrent) == True + + +def test_nyaa_get_from_user(): + request = Nyaa.get_from_user(username="Erai-raws") + torrent = request[0] + + assert isinstance(torrent, Torrent) == True + assert len(request) <= 75 \ No newline at end of file diff --git a/tests/test.py b/tests/test.py deleted file mode 100644 index b473619..0000000 --- a/tests/test.py +++ /dev/null @@ -1,74 +0,0 @@ -from nyaapy.nyaa import Nyaa -from pprint import pprint -from datetime import datetime -import json -import sys -import os - -# Creating a folder for test_files -# ! not included in github project. -if not os.path.isdir("test_files"): - os.makedirs("test_files") - -nyaa = Nyaa() - -# Get fresh torrents -dt_latest_torrents_begin = datetime.now() -latest_torrents = nyaa.last_uploads(100) -dt_latest_torrents_end = datetime.now() -with open("test_files/nyaa_latest_torrent_test.json", "w") as f: - for torrent in latest_torrents: - try: - # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode("utf-8")) + "\n") - except AttributeError: - f.write("No name found for this torrent") - -# Search some nasty stuff -dt_search_begin = datetime.now() -test_search = nyaa.search("kimi no na wa") -dt_search_end = datetime.now() -with open("test_files/nyaa_search_test.json", "w") as f: - for torrent in test_search: - try: - # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode("utf-8")) + "\n") - except AttributeError: - f.write("No name found for this torrent") - -# Get first torrent from found torrents -dt_single_torrent_begin = datetime.now() -single_torrent = test_search[0] -dt_single_torrent_end = datetime.now() -with open("test_files/nyaa_single_torrent_test.json", "w") as f: - try: - # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode("utf-8")) + "\n") - except AttributeError: - f.write("No name found for this torrent") - -dt_user_begin = datetime.now() -user_torrents = nyaa.get_user("HorribleSubs") -dt_user_end = datetime.now() -with open("test_files/nyaa_single_user_test.json", "w") as f: - for torrent in user_torrents: - try: - # This prints it as byte like objects since unicode is fun - f.write(str(torrent.name.encode("utf-8")) + "\n") - except AttributeError: - f.write("No name found for this torrent") - -print( - "Latest torrents time:", - (dt_latest_torrents_end - dt_latest_torrents_begin).microseconds / 1000, - "msec", -) -print( - "Test search time:", (dt_search_end - dt_search_begin).microseconds / 1000, "msec" -) -print( - "Single torrent time:", - (dt_single_torrent_end - dt_single_torrent_begin).microseconds / 1000, - "msec", -) -print("Single user time:", (dt_user_end - dt_user_begin).microseconds / 1000, "msec") diff --git a/tests/test_pantsu.py b/tests/test_pantsu.py deleted file mode 100644 index fdaf4e8..0000000 --- a/tests/test_pantsu.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -* Pantsu need some serious work -Regular data single_torrent parser not working from other Nyaa alternatives -Needs some work -""" - -print("TODO") diff --git a/tests/test_sukebei.py b/tests/test_sukebei.py deleted file mode 100644 index add9179..0000000 --- a/tests/test_sukebei.py +++ /dev/null @@ -1,53 +0,0 @@ -from nyaapy.sukebei import SukebeiNyaa -from datetime import datetime -import json -import os - -# Creating a folder for test_files -# ! not included in github project. -if not os.path.isdir("test_files"): - os.makedirs("test_files") - -nyaa = SukebeiNyaa() - -# Get fresh torrents -dt_latest_torrents_begin = datetime.now() -latest_torrents = nyaa.last_uploads(100) -dt_latest_torrents_end = datetime.now() -with open("test_files/sukebei_latest_torrent_test.json", "w") as f: - json.dump(latest_torrents, f) - -# Search some nasty stuff -dt_search_begin = datetime.now() -test_search = nyaa.search("G Senjou no maou") -dt_search_end = datetime.now() -with open("test_files/sukebei_search_test.json", "w") as f: - json.dump(test_search, f) - -# Get first torrent from found torrents -dt_single_torrent_begin = datetime.now() -single_torrent = nyaa.get(test_search[0]["id"]) -dt_single_torrent_end = datetime.now() -with open("test_files/sukebei_single_torrent_test.json", "w") as f: - json.dump(single_torrent, f) - -dt_user_begin = datetime.now() -user_torrents = nyaa.get_user("RUNBKK") -dt_user_end = datetime.now() -with open("test_files/sukebei_single_user_test.json", "w") as f: - json.dump(user_torrents, f) - -print( - "Latest torrents time:", - (dt_latest_torrents_end - dt_latest_torrents_begin).microseconds / 1000, - "msec", -) -print( - "Test search time:", (dt_search_end - dt_search_begin).microseconds / 1000, "msec" -) -print( - "Single torrent time:", - (dt_single_torrent_end - dt_single_torrent_begin).microseconds / 1000, - "msec", -) -print("Single user time:", (dt_user_end - dt_user_begin).microseconds / 1000, "msec") From a68eee03243eca0995d8d1a05633b8966e059be8 Mon Sep 17 00:00:00 2001 From: Juanjo Salvador Date: Thu, 18 Jul 2024 09:48:13 +0000 Subject: [PATCH 3/4] chore(style): Black style --- LICENSE.md | 2 +- nyaapy/anime_site.py | 1 + nyaapy/magnet.py | 3 ++- nyaapy/nyaasi/nyaa.py | 2 +- nyaapy/nyaasi/sukebei.py | 3 ++- nyaapy/parser.py | 1 + nyaapy/torrent.py | 6 ++++-- tests/integration/test_nyaasi.py | 5 +++-- 8 files changed, 15 insertions(+), 8 deletions(-) diff --git a/LICENSE.md b/LICENSE.md index 3d4a186..af46a89 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright 2017 Juanjo Salvador +Copyright 2024 Juanjo Salvador Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/nyaapy/anime_site.py b/nyaapy/anime_site.py index e94acbd..7cd8844 100644 --- a/nyaapy/anime_site.py +++ b/nyaapy/anime_site.py @@ -2,6 +2,7 @@ import requests from nyaapy import torrent from nyaapy.parser import parse_nyaa, parse_single, parse_nyaa_rss + class AnimeTorrentSite: SITE = torrent.TorrentSite.NYAASI URL = "https://nyaa.si" diff --git a/nyaapy/magnet.py b/nyaapy/magnet.py index f89849b..f529b2f 100644 --- a/nyaapy/magnet.py +++ b/nyaapy/magnet.py @@ -1,6 +1,7 @@ import urllib from urllib.parse import urlencode + def magnet_builder(info_hash, title): """ Generates a magnet link using the info_hash and title of a given file. @@ -19,4 +20,4 @@ def magnet_builder(info_hash, title): for tracker in known_trackers: magnet_link += f"&{urlencode({'tr': tracker})}" - return magnet_link \ No newline at end of file + return magnet_link diff --git a/nyaapy/nyaasi/nyaa.py b/nyaapy/nyaasi/nyaa.py index 76c24ec..ccf5b01 100644 --- a/nyaapy/nyaasi/nyaa.py +++ b/nyaapy/nyaasi/nyaa.py @@ -4,4 +4,4 @@ from nyaapy.torrent import TorrentSite class Nyaa(AnimeTorrentSite): SITE = TorrentSite.NYAASI - URL = "https://nyaa.si" \ No newline at end of file + URL = "https://nyaa.si" diff --git a/nyaapy/nyaasi/sukebei.py b/nyaapy/nyaasi/sukebei.py index 7f63b77..68bf16f 100644 --- a/nyaapy/nyaasi/sukebei.py +++ b/nyaapy/nyaasi/sukebei.py @@ -1,6 +1,7 @@ from nyaapy.anime_site import AnimeTorrentSite from nyaapy.torrent import TorrentSite + class SukebeiNyaa(AnimeTorrentSite): SITE = TorrentSite.SUKEBEINYAASI - URL = "https://sukebei.nyaa.si" \ No newline at end of file + URL = "https://sukebei.nyaa.si" diff --git a/nyaapy/parser.py b/nyaapy/parser.py index 93ddb7f..836ec1e 100644 --- a/nyaapy/parser.py +++ b/nyaapy/parser.py @@ -2,6 +2,7 @@ from lxml import etree from nyaapy.magnet import magnet_builder from nyaapy.torrent import TorrentSite + def nyaa_categories(b): c = b.replace("?c=", "") cats = c.split("_") diff --git a/nyaapy/torrent.py b/nyaapy/torrent.py index a5af209..3aca6f3 100644 --- a/nyaapy/torrent.py +++ b/nyaapy/torrent.py @@ -12,12 +12,14 @@ def json_to_class(data): else: return Torrent(data) + # This deals with converting the dict to an object class Torrent(object): def __init__(self, my_dict): for key in my_dict: setattr(self, key, my_dict[key]) + class TorrentSite(Enum): """ Contains torrent sites @@ -25,5 +27,5 @@ class TorrentSite(Enum): NYAASI = "https://nyaa.si" SUKEBEINYAASI = "https://sukebei.nyaa.si" - - NYAALAND = "https://nyaa.land" \ No newline at end of file + + NYAALAND = "https://nyaa.land" diff --git a/tests/integration/test_nyaasi.py b/tests/integration/test_nyaasi.py index e05b2ff..2b477a4 100644 --- a/tests/integration/test_nyaasi.py +++ b/tests/integration/test_nyaasi.py @@ -1,6 +1,7 @@ from nyaapy.nyaasi.nyaa import Nyaa from nyaapy.torrent import Torrent + def test_nyaa_last_uploads(): request = Nyaa.last_uploads(number_of_results=10) torrent = request[0] @@ -17,7 +18,7 @@ def test_nyaa_search(): def test_nyaa_get_single(): - request = Nyaa.get(view_id='1847113') + request = Nyaa.get(view_id="1847113") assert isinstance(request, Torrent) == True @@ -27,4 +28,4 @@ def test_nyaa_get_from_user(): torrent = request[0] assert isinstance(torrent, Torrent) == True - assert len(request) <= 75 \ No newline at end of file + assert len(request) <= 75 From b1cd340096e84197f9dca562f020817b9d938446 Mon Sep 17 00:00:00 2001 From: Juanjo Salvador Date: Thu, 18 Jul 2024 09:50:19 +0000 Subject: [PATCH 4/4] Bump to 0.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 37f942e..8cf2911 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "nyaapy" -version = "0.6.3" +version = "0.7" description = "Unofficial Python wrapper for NyaaPantsu API and Nyaa.si" authors = ["Juanjo Salvador "] license = "MIT"