diff --git a/quasarr/downloads/sources/dt.py b/quasarr/downloads/sources/dt.py index bb9f5641..a6216863 100644 --- a/quasarr/downloads/sources/dt.py +++ b/quasarr/downloads/sources/dt.py @@ -33,15 +33,7 @@ def get_download_links(self, shared_state, url, mirrors, title, password): r.raise_for_status() soup = BeautifulSoup(r.text, "html.parser") - article = soup.find("article") - if not article: - info(f"Could not find article block for {title}") - mark_hostname_issue( - Source.initials, "download", "Could not find article block" - ) - return None - - body = article.find("div", class_="full-story-body") + body = soup.find("div", class_="full-story-body") if not body: info(f"Could not find download section for {title}") mark_hostname_issue( diff --git a/quasarr/providers/version.py b/quasarr/providers/version.py index 6e56c273..fd701d81 100644 --- a/quasarr/providers/version.py +++ b/quasarr/providers/version.py @@ -5,7 +5,7 @@ import re import sys -__version__ = "4.7.8" +__version__ = "4.7.9" def get_version(): diff --git a/quasarr/search/sources/dt.py b/quasarr/search/sources/dt.py index 6e3d6772..14d9564d 100644 --- a/quasarr/search/sources/dt.py +++ b/quasarr/search/sources/dt.py @@ -80,7 +80,7 @@ def feed( for article in feed.find_all("article"): try: - link_tag = article.select_one("h1.dt-flexible-title a") + link_tag = article.select_one(".dt-flexible-title a") if not link_tag: # ad/promo articles lack a release title link; skip quietly debug(f"Link tag not found in article: {article}") @@ -238,7 +238,7 @@ def search( for article in page.find_all("article"): try: # search-result articles use h1.font-weight-bold, while - # the feed listing uses h1.dt-flexible-title + # the feed listing uses .dt-flexible-title link_tag = article.select_one("h1.font-weight-bold a") if not link_tag: debug(f"No title link in search-article: {article}") @@ -340,24 +340,29 @@ def _extract_size(text): def _parse_published_datetime(article): - mon = article.select_one("small.dt-fs-11").text.strip() - day = article.find("h4", class_="m-0").text.strip() - year = article.find("h6", class_="m-0").text.strip() - month_num = datetime.datetime.strptime(mon, "%b").month - - time_icon = article.select_one("i.bi-clock") - if time_icon: - # its parent contains e.g. "19:12" - raw = time_icon.parent.get_text(strip=True) - m = re.search(r"(\d{1,2}:\d{2})", raw) - if m: - hh, mm = map(int, m.group(1).split(":")) - else: - hh, mm = 0, 0 + month_tag = article.select_one("small.dt-fs-11") + if not month_tag: + # feed listings show "29.10.2026 07:47" instead of the calendar block + m = re.search( + r"\b(\d{2})\.(\d{2})\.(\d{4})\s+(\d{1,2}):(\d{2})\b", + article.get_text(" "), + ) + day, month_num, year, hh, mm = map(int, m.groups()) else: + day = int(article.find("h4", class_="m-0").text.strip()) + year = int(article.find("h6", class_="m-0").text.strip()) + month_num = datetime.datetime.strptime(month_tag.text.strip(), "%b").month + + time_icon = article.select_one("i.bi-clock") hh, mm = 0, 0 + if time_icon: + # its parent contains e.g. "19:12" + raw = time_icon.parent.get_text(strip=True) + m = re.search(r"(\d{1,2}:\d{2})", raw) + if m: + hh, mm = map(int, m.group(1).split(":")) # this timezone is fixed to CET+1 and might be wrong cet = timezone(timedelta(hours=1)) - dt = datetime.datetime(int(year), month_num, int(day), hh, mm, tzinfo=cet) + dt = datetime.datetime(year, month_num, day, hh, mm, tzinfo=cet) return dt.isoformat() diff --git a/quasarr/search/sources/he.py b/quasarr/search/sources/he.py index 7ed077da..0601f9fc 100644 --- a/quasarr/search/sources/he.py +++ b/quasarr/search/sources/he.py @@ -188,6 +188,8 @@ def search( search_type, str(e) if "e" in dir() else "Error occurred", ) + # the relative "Posted N days ago" drifts with every search + published = _parse_published_meta(soup) or published release_imdb_id = None try: imdb_link = soup.find( @@ -245,6 +247,17 @@ def search( return releases +def _parse_published_meta(soup): + meta = soup.find("meta", attrs={"itemprop": "datePublished"}) + try: + published = datetime.fromisoformat(meta["content"]) + return published.astimezone(timezone.utc).strftime( + "%a, %d %b %Y %H:%M:%S +0000" + ) + except Exception: + return "" + + def _parse_posted_ago(txt): try: m = re.search( diff --git a/tests/test_dt_source.py b/tests/test_dt_source.py new file mode 100644 index 00000000..e2eab0c0 --- /dev/null +++ b/tests/test_dt_source.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +import unittest +from unittest.mock import MagicMock, patch + +from bs4 import BeautifulSoup + +from quasarr.downloads.sources import dt +from quasarr.search.sources.dt import _parse_published_datetime + + +def _response(html): + response = MagicMock() + response.text = html + return response + + +class DtDownloadLinksTests(unittest.TestCase): + def _get_links(self, html): + shared_state = MagicMock() + shared_state.values = {"user_agent": "test-agent"} + session = MagicMock() + session.get.return_value = _response(html) + with ( + patch.object(dt.requests, "Session", return_value=session), + patch.object(dt, "clear_hostname_issue"), + patch.object(dt, "mark_hostname_issue"), + ): + return dt.Source().get_download_links( + shared_state, + "https://source.invalid/post.html", + None, + "Example.Show.S01E01.1080p.WEB.h264-GROUP", + "source.invalid", + ) + + def test_finds_story_body_outside_article(self): + result = self._get_links( + """ +
Example
+
+ one + imdb +
+ """ + ) + + self.assertEqual( + {"links": [["https://rapidgator.invalid/file/one", "rapidgator"]]}, + result, + ) + + def test_finds_story_body_inside_article(self): + result = self._get_links( + """ + + """ + ) + + self.assertEqual( + {"links": [["https://ddownload.invalid/file/two", "ddownload"]]}, + result, + ) + + +class DtPublishedDateTests(unittest.TestCase): + def _parse(self, html): + article = BeautifulSoup(html, "html.parser").find("article") + return _parse_published_datetime(article) + + def test_parses_feed_listing_date(self): + self.assertEqual( + "2026-10-29T07:47:00+01:00", + self._parse( + """ +
+ Today, 07:47 + 29.10.2026 07:47 +
+ """ + ), + ) + + def test_parses_search_calendar_block(self): + self.assertEqual( + "2026-10-24T14:39:00+01:00", + self._parse( + """ +
+ Oct +

24

+
2026
+ 14:39 +
+ """ + ), + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_he_published_date.py b/tests/test_he_published_date.py new file mode 100644 index 00000000..c1bdfeac --- /dev/null +++ b/tests/test_he_published_date.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import time +import unittest +from unittest.mock import MagicMock, patch + +from quasarr.search.sources import he + +POST_URL = "https://source.invalid/synthetic-show-s01e01/" + +SEARCH_HTML = f""" + +""" + + +def _response(html): + response = MagicMock() + response.content = html.encode("utf-8") + return response + + +class HePublishedDateTests(unittest.TestCase): + def _search(self, post_html): + shared_state = MagicMock() + shared_state.values = { + "user_agent": "test-agent", + "config": lambda section: {"he": "source.invalid"}, + } + + def get(url, **kwargs): + return _response(post_html if url == POST_URL else SEARCH_HTML) + + with ( + patch.object(he.requests, "get", side_effect=get), + patch.object(he, "generate_download_link", return_value="link"), + patch.object(he, "clear_hostname_issue"), + patch.object(he, "mark_hostname_issue"), + ): + releases = he.Source().search(shared_state, time.time(), 5000) + return [release["details"]["date"] for release in releases] + + def test_uses_absolute_published_date_from_post(self): + self.assertEqual( + ["Thu, 23 Oct 2031 10:11:40 +0000"], + self._search( + '' + ), + ) + + def test_falls_back_to_relative_post_time(self): + with patch.object(he, "_parse_posted_ago", return_value="relative"): + self.assertEqual(["relative"], self._search("
")) + + +if __name__ == "__main__": + unittest.main() diff --git a/uv.lock b/uv.lock index 6ed7b39c..08be7267 100644 --- a/uv.lock +++ b/uv.lock @@ -387,11 +387,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.13.5" +version = "4.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/e6/d0abbb256b44e6a05748c6942511ff67db8dbb068de0c35b24bf1b395b09/platformdirs-4.13.5.tar.gz", hash = "sha256:05831adf87d392760f4b5c3a0a95579ab5a10b18b9b9bd43680dcb47544788a0", size = 62362, upload-time = "2026-10-19T01:02:11.778Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/28/1c058a9143d2304b3078f8dadcbb9dc27f7d3581b5d05157c84869c4cc0d/platformdirs-4.14.0.tar.gz", hash = "sha256:83f009c1e8c5fc36869b5171755b291887bd2cade7965233cdfabd23d754720b", size = 70387, upload-time = "2026-10-28T18:27:30.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/8a/1d8cb3842909dc193a73835adedf412e32e18f8ffd59f43983949622fe6f/platformdirs-4.13.5-py3-none-any.whl", hash = "sha256:53d3ec4280b815c1e0b02edad7f9ee8fda1ba9c0865dd9c2e6e63849ce82fc1e", size = 32779, upload-time = "2026-10-19T01:02:10.17Z" }, + { url = "https://files.pythonhosted.org/packages/69/2b/887969ef4bb53e0cb271dc7c3f4a73d50c5d58bb88aeaae311e3340cd57e/platformdirs-4.14.0-py3-none-any.whl", hash = "sha256:1dadfc59a8a5d7565b3d9270ae8fa71c065595352c4277b41c11ee6c222c4b91", size = 35259, upload-time = "2026-10-28T18:27:29.659Z" }, ] [[package]] @@ -508,14 +508,14 @@ wheels = [ [[package]] name = "python-discovery" -version = "1.6.4" +version = "1.6.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/ee/cf608372c88ce2c136ee13c69ab6f667dbc0d0c1176af188b4ddc8853e66/python_discovery-1.6.4.tar.gz", hash = "sha256:bc70d31f0ec5de11eda13d4d64c16e24b3506a65984684f2b6c98cb83b2ea9df", size = 89883, upload-time = "2026-10-19T01:45:14.855Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/0b/908da9ef8a14b600fc188eee8d63ede071996114613721153f6fc9ea3cd5/python_discovery-1.6.7.tar.gz", hash = "sha256:7f7c28568c8ec3d988d8bddbdbec8e65f08e8767e52f734e18b8b498549b9b70", size = 117807, upload-time = "2026-10-28T06:24:21.878Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/91/1aa70d3dfcab0dbcf88a2cbc3bce7f7c69562287a90216e617eaa4937e25/python_discovery-1.6.4-py3-none-any.whl", hash = "sha256:530179cc51a1816d512b15635b3dc931295ae12efb183783ad31190a5d425d2b", size = 39385, upload-time = "2026-10-19T01:45:13.668Z" }, + { url = "https://files.pythonhosted.org/packages/57/e4/1c7c388e874222c28afb92fe3ebfeebe55555276523ba7aab0a82a10b2b5/python_discovery-1.6.7-py3-none-any.whl", hash = "sha256:5422f67e5f533bb02d7b73de1c422ed74037536fd869e05156c07565926ec68e", size = 39156, upload-time = "2026-10-28T06:24:20.462Z" }, ] [[package]] @@ -680,27 +680,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.17.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/58/690cf56da80c8f2c8bf12042b6017beac2c34fe961f65420bcb3fb7fac38/ruff-0.17.2.tar.gz", hash = "sha256:84d632402d14c23b488f941db23712d6736a013225162062d34558c3f3c780eb", size = 5333400, upload-time = "2026-10-22T18:38:00.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/b8/8b6d69c57e8fe1cc6af1aa7493b72a1a5b5a8afe34694e302025441729d4/ruff-0.17.2-py3-none-linux_armv6l.whl", hash = "sha256:96a65e755d8dba094707fb23ab602111d9a31ec2835b3a6992a1914a8fcb211e", size = 10183857, upload-time = "2026-10-22T18:37:14.956Z" }, - { url = "https://files.pythonhosted.org/packages/e8/61/12cc2ff7b2fa90a268ce1e2162c23aaf76e601d22623f6a31b0eeaa764b0/ruff-0.17.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:39c5b55c960515cdb32be16e69efb5734c589c06a0eb38a22694524a336b889d", size = 10210904, upload-time = "2026-10-22T18:37:17.985Z" }, - { url = "https://files.pythonhosted.org/packages/e8/7c/b8850a8e4ad29f64a827dd6c6902db4a9aa538fb2b9eef4e26084001a3bb/ruff-0.17.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:92a80d2f44584dabb0189b3a6c393e37e525e04942838482010575cb81945be7", size = 9408439, upload-time = "2026-10-22T18:37:20.964Z" }, - { url = "https://files.pythonhosted.org/packages/b7/b6/6a64fae86ca5e5e29d5adc311e5481de726cedf9aee607435a16c31c442a/ruff-0.17.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7d0906677d89bbf3cf73cde64b5bbe2ab35df3ae43e49ee2627301e91265bac", size = 10047078, upload-time = "2026-10-22T18:37:23.717Z" }, - { url = "https://files.pythonhosted.org/packages/10/19/23d376547715175aa1bd46df48df6aacc7c6439d9daf19cac15975180aa5/ruff-0.17.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7cfd783590edf2b77317882631900f6a13ea2e8eacefe6ebf2370d2715299160", size = 9990478, upload-time = "2026-10-22T18:37:26.584Z" }, - { url = "https://files.pythonhosted.org/packages/93/6c/34cda32cbe13adac5c6b291a307a5570d8ebd37c0c86339f0fe628e55632/ruff-0.17.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122258f6ecde0e0dd1bb60b24263c3edde4338caf723fca711cc729f9bfd414a", size = 10673624, upload-time = "2026-10-22T18:37:29.767Z" }, - { url = "https://files.pythonhosted.org/packages/bf/10/5d762a4dbc851d8bb47863cf3a28a61bf31c804d393b70d891fac899f5d1/ruff-0.17.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f8fba429d6ac4128ab29cfb8fa6240d2fa671d04adf7f84d3b22fc7b6b6bea4", size = 11351797, upload-time = "2026-10-22T18:37:32.535Z" }, - { url = "https://files.pythonhosted.org/packages/3f/04/a0e92a81faf5c81c1c98d4374c8401734ccf2b8cca7d0170cb0fec08c6e2/ruff-0.17.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:583bd67bb9e7061dd09bc46a0a884bae2bbd214dbcd48e509813612619683869", size = 10949414, upload-time = "2026-10-22T18:37:35.501Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d8/59894cf630d2d86882457a2a6a0a1533410c5c805bfbac6f6eb4b5aef69d/ruff-0.17.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c60f93fec7142dba5b8f487f94bf2f6231dae24dc11e4c48648347c8b0720004", size = 10528000, upload-time = "2026-10-22T18:37:37.88Z" }, - { url = "https://files.pythonhosted.org/packages/ab/95/d6e30c51e851d37e10762ad91fa1e76ddaad998589b5667962a3cfbc9edd/ruff-0.17.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c555a877f8f41447e318101cfefea9bf12dfa9f82eedf90c376e57e069f917d0", size = 10571779, upload-time = "2026-10-22T18:37:40.444Z" }, - { url = "https://files.pythonhosted.org/packages/f4/5a/a7e1d74fb464250137d3d4eca645d323536533b672004d04ee0e46e0caf3/ruff-0.17.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ba405cbd5ec7bda1f555d5e52be1fd68d02608d4d95d24591bdac92cfacb579f", size = 10051707, upload-time = "2026-10-22T18:37:42.812Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f2/02fca29ca7999380f0350a7212fe755c11ab9c22d61327356cce8decdbc6/ruff-0.17.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8c21b4ab24485358f17291a4f8dc5399b834e963b8efedd920fe4fdb02081bb4", size = 9998227, upload-time = "2026-10-22T18:37:45.224Z" }, - { url = "https://files.pythonhosted.org/packages/d1/00/55aa604a093dfdc1526e570fae0bb7d37a2221c7b4728d5f080b2add16ba/ruff-0.17.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5875a16b0de70b43cb81eabea5ae5e8f5d1b2fd1b5e6ed07f80cfe5019d0bd5a", size = 10377692, upload-time = "2026-10-22T18:37:47.704Z" }, - { url = "https://files.pythonhosted.org/packages/e4/5a/a7a640f47bb906ec8fa233ea84319d503e6dee02b59e3901bd312ebd1f9b/ruff-0.17.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2d086be452eed686aa51271ecf2cbe5037956517cc6ee39a17b4489ac12f8bbe", size = 10790587, upload-time = "2026-10-22T18:37:50.191Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d4/c3b74e1b4808438ea7f14bb914478025e2d51261f60774a54a0fa222ec46/ruff-0.17.2-py3-none-win32.whl", hash = "sha256:6bce7d1f5165701ae18f5df85c65afe78cddfa6c5fb4b9bdb0014d60388f3edd", size = 9870537, upload-time = "2026-10-22T18:37:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9a/c7da96c2c96d20704cf9027f7a5121da272a8c05f1d1531ba949e3b3df04/ruff-0.17.2-py3-none-win_amd64.whl", hash = "sha256:1b59d447f197bef9ef48a22ba1a6016137753c6099e9bac9c36eb566e422424c", size = 10282826, upload-time = "2026-10-22T18:37:55.2Z" }, - { url = "https://files.pythonhosted.org/packages/81/60/a3604c79cb02777b6cb84da894104021986f06c4bc41375102cc95049a32/ruff-0.17.2-py3-none-win_arm64.whl", hash = "sha256:195ad918027f817c119310ca89a42d347887dfc28934aaa869325e9c5dc9d962", size = 9827036, upload-time = "2026-10-22T18:37:58.031Z" }, +version = "0.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/59/73/81f140c4edd9cc323de5478e6bbd3bbcfae9a82692df1a258de8cec6a6d2/ruff-0.17.3.tar.gz", hash = "sha256:a57049c8addb3662073859bb55a7ad35aa54f8a7c9ed268911d1333c5aadaaa9", size = 5339444, upload-time = "2026-10-27T00:34:05.914Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/8c/083365310c4e79c842f25bf5a842facbd9b0c0ec336f96dffa5074fe723d/ruff-0.17.3-py3-none-linux_armv6l.whl", hash = "sha256:ab86b84cd9d05e56d329742c0cabdcbe2d1d56c98f4a04aab9b5b66196febfb4", size = 10216522, upload-time = "2026-10-27T00:33:29.822Z" }, + { url = "https://files.pythonhosted.org/packages/72/26/4b2edf31cfebe9605d8f6ed8f1d6f376d3a53d4c9a123e62661c64c440f0/ruff-0.17.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e407b4a6852853a9a2cdce96963e94ad9775578bb44e7561a0f2829f3cc04b11", size = 10217786, upload-time = "2026-10-27T00:33:33.949Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7e/537aba4b7f54397a374d2f6a6fc93e9c8015e18553f31a4a7b7dbced48b5/ruff-0.17.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b75cc113b3aca388933d8296b667b1ac401a80317ea3074c9b0a66541199da29", size = 9440362, upload-time = "2026-10-27T00:33:35.784Z" }, + { url = "https://files.pythonhosted.org/packages/3d/66/283e39734f66c8a5ebaa93c22eb7b7515779a0b1d5a5c0ad20e05c0edb41/ruff-0.17.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab5fa7ed5ce18554ea69d850ad162cbaa0c85fc47c08d83a569f806681c9a6ad", size = 10094281, upload-time = "2026-10-27T00:33:38.216Z" }, + { url = "https://files.pythonhosted.org/packages/21/4c/1ffc768497bd025a32c72152771e40c0b99bddc5cc6e4f50c7949a395346/ruff-0.17.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c673c170e27834d1262e3a9c1e97bdc9b9c3b180c63d89173ce69747232474c1", size = 10022543, upload-time = "2026-10-27T00:33:40.181Z" }, + { url = "https://files.pythonhosted.org/packages/73/c3/f90db547301080fcb82c92abf914c8bd57b89911d9b65a6c4cc6b52d0635/ruff-0.17.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a754c0341bc9ca151b7b335988b3263921c08859f84ca219b70cee825571b6f", size = 10683921, upload-time = "2026-10-27T00:33:42.182Z" }, + { url = "https://files.pythonhosted.org/packages/e5/98/92d4a8a26c6c96cf1a5b34dd59036d8a71e52d650aab065c58867e964e63/ruff-0.17.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4590b9d86c0b1543ebb09094254e191cd43fe8edaf330baa035aab03da251532", size = 11373408, upload-time = "2026-10-27T00:33:44.19Z" }, + { url = "https://files.pythonhosted.org/packages/43/02/1f24d2e3064c9753926deeb901afa57777b37ee46799b09ddd7aee6e5145/ruff-0.17.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bbde18beccd98c29a227c63c99022e3443b99258e08326df6425dfe0443705ef", size = 10977469, upload-time = "2026-10-27T00:33:46.179Z" }, + { url = "https://files.pythonhosted.org/packages/f0/20/fecedea2209f574ab92033b344e01a7f0c2dc76c6d6d19a14c9f8141173f/ruff-0.17.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fa4b0f931cdbcb8a52044f370a3eb429fb42eb88807b0e800fcaa98dd42b24e", size = 10577491, upload-time = "2026-10-27T00:33:48.233Z" }, + { url = "https://files.pythonhosted.org/packages/33/36/b93fdf0bc6f84f0cd487aadfb0354deb40013669ea35ac3d26855de1e959/ruff-0.17.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:39405f341deec934b4289634324ad695f72df2970f9121582872eaf5b722f0f5", size = 10608748, upload-time = "2026-10-27T00:33:50.284Z" }, + { url = "https://files.pythonhosted.org/packages/5d/b7/7d3f5f14145daead1efb0b9a8289ed1daa100f419d60774375951723ce00/ruff-0.17.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68074094f55ca3d29ab19491f640282f7a84061c4c7977aa5094b968ca457e9a", size = 10075057, upload-time = "2026-10-27T00:33:52.074Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ed/93ff7a5909ee56e2bd4b982deae9e0efb2724417874a90c9e41ad66c6dd7/ruff-0.17.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:27bb571ac78fef178fe5118d2827df95c2aae74a48fff2b15f6acd03af5ff5bb", size = 10022431, upload-time = "2026-10-27T00:33:54.061Z" }, + { url = "https://files.pythonhosted.org/packages/9b/07/a83b9eeec8c1c3c0e4eda15f06c3a91001bfd2687ea2f07b750719bf9712/ruff-0.17.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:029c37cbd3de48ca37aa12117bead2d065f15da17d66bf84e0d4274013705db2", size = 10394903, upload-time = "2026-10-27T00:33:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/af/b2/05c39857c2526fcbebe46ad93a8dc97424656dfef14d3ef933abe1ffd331/ruff-0.17.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:692fdb46d0fd9c71e380947dc5fc72f2c3f881073bc1f739e4579376231affbe", size = 10799780, upload-time = "2026-10-27T00:33:58.204Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d9/47171a16739eb7a353936b31c20cf54340531e68c9af7d72fd7a784e235d/ruff-0.17.3-py3-none-win32.whl", hash = "sha256:46539cb031b01fd29ae0a02142483c46640b233ee12bb1c1b690bffa1555d5ca", size = 9908481, upload-time = "2026-10-27T00:34:00.087Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a0/f08c1631bfd81faa6a90eaf70c37c786c11bed2046302b56bb506c25e1a0/ruff-0.17.3-py3-none-win_amd64.whl", hash = "sha256:56b8ffb3f3b3cbccb8b76b71b38261b7ed982ef5d20f5b447b1b61a7f4949b24", size = 10341333, upload-time = "2026-10-27T00:34:02.099Z" }, + { url = "https://files.pythonhosted.org/packages/99/d5/4ad4ed31413c301430918e70d3d38ae6f097af0766cb9ab8c9d502b2a4d6/ruff-0.17.3-py3-none-win_arm64.whl", hash = "sha256:81fce74bcd6be0e9da7c67c7553fd0f1f12964a2052cd728ef917b937a5ca1d9", size = 9854332, upload-time = "2026-10-27T00:34:03.847Z" }, ] [[package]] @@ -741,7 +741,7 @@ wheels = [ [[package]] name = "virtualenv" -version = "21.14.13" +version = "21.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, @@ -750,9 +750,9 @@ dependencies = [ { name = "platformdirs" }, { name = "python-discovery" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/aa/bff50daee0488e16d611258a9b6c5a1d0c9e128f8ca424e9f4e1c0444ef2/virtualenv-21.14.13.tar.gz", hash = "sha256:de5307ba4b9a0001d9c8ea02832d38f63ca432880f416c1bface61c4323fd905", size = 5570301, upload-time = "2026-10-22T20:10:16.952Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/24/20282bc8e69ff8dee680dc2b95e9dc81fa7e7894b14c1a8d06330e97fef7/virtualenv-21.15.1.tar.gz", hash = "sha256:e79c90b8f601701599c25905e02839924fede2c798d52325670d24fbdaab74ee", size = 5517048, upload-time = "2026-10-28T01:12:50.919Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/a9/c10d8a44a29fb94a18b43fdf11f38caca53ae7c9406f11a72b1cb8f624f1/virtualenv-21.14.13-py3-none-any.whl", hash = "sha256:5fbb04138a4861580b78ba9110d529a26bc48fcc93c8ec36bc0202eeed96a788", size = 5557094, upload-time = "2026-10-22T20:10:15.096Z" }, + { url = "https://files.pythonhosted.org/packages/ce/23/cdab19d3a9da42ca0938081e163e25f81e610141800ead743ba1ee881f0e/virtualenv-21.15.1-py3-none-any.whl", hash = "sha256:0d14a991689c67600316880eadc0a37b7690cb632e3b4ac67e2bc05b2b2e24a4", size = 5558848, upload-time = "2026-10-28T01:12:48.605Z" }, ] [[package]]