diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 00000000000..adf5804ebbd --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,23 @@ +name: Ruff + +on: + pull_request: + push: + branches: + - master + +permissions: + contents: read + +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v6 + - name: Install dependencies + run: uv sync --group dev + - name: Ruff check + run: uv run ruff check . --format=github diff --git a/main.py b/main.py index 7628fef50d0..613ea0ffea9 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,8 @@ -from pathlib import Path -import shutil -import asyncio import argparse +import asyncio import logging +import shutil +from pathlib import Path from src import make_curriculum, make_rss diff --git a/pyproject.toml b/pyproject.toml index 0a2295f5be3..50f3d3ab27c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,34 @@ dev = [ "jupyter>=1.1.1", "notebook>=7.4.7", "pandas>=2.3.3", + "ruff>=0.14.14", ] [project.scripts] static-build = "main:main" + +[tool.ruff] +target-version = "py313" +line-length = 88 +extend-exclude = ["build", "static"] +lint.select = [ + "B", + "E", + "F", + "I", + "N", + "SIM", + "UP", +] + +[tool.ruff.lint.per-file-ignores] +"src/models/*.py" = ["N815"] +"src/utils/catalog.py" = ["N806"] +"src/utils/jw.py" = ["N802", "N806", "N816"] +"src/utils/tj_rss.py" = ["N802"] +"tools/bus_data_gen.py" = ["N816"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +line-ending = "lf" diff --git a/src/curriculum.py b/src/curriculum.py index 2c3c4cfe315..7c2e029e96b 100644 --- a/src/curriculum.py +++ b/src/curriculum.py @@ -1,13 +1,13 @@ import asyncio -import logging from pathlib import Path + from tqdm import tqdm from .models.course import Course -from .utils.catalog import get_semesters, get_courses, get_exams +from .utils.auth import RequestSession, USTCSession +from .utils.catalog import get_courses, get_exams, get_semesters from .utils.jw import update_lectures -from .utils.tools import save_json, BUILD_DIR -from .utils.auth import USTCSession, RequestSession +from .utils.tools import BUILD_DIR, save_json async def fetch_semester( @@ -36,7 +36,7 @@ async def fetch_semester( exams = {} for course in incomplete_courses: - if course.id in exams.keys(): + if course.id in exams: course.exams = exams[course.id] sem = asyncio.Semaphore(50) diff --git a/src/models/course.py b/src/models/course.py index 073a20f1946..763de5847ba 100644 --- a/src/models/course.py +++ b/src/models/course.py @@ -1,8 +1,8 @@ -from typing import Optional + from pydantic import BaseModel -from .lecture import Lecture from .exam import Exam +from .lecture import Lecture class Course(BaseModel): @@ -13,8 +13,8 @@ class Course(BaseModel): teacherName: str lectures: list[Lecture] exams: list[Exam] - dateTimePlacePersonText: Optional[str] - courseType: Optional[str] + dateTimePlacePersonText: str | None + courseType: str | None courseGradation: str courseCategory: str educationType: str diff --git a/src/models/exam.py b/src/models/exam.py index 59a5cef331e..2cbe38f65df 100644 --- a/src/models/exam.py +++ b/src/models/exam.py @@ -1,5 +1,5 @@ + from pydantic import BaseModel -from typing import Optional class Exam(BaseModel): @@ -10,5 +10,5 @@ class Exam(BaseModel): examType: str startHHMM: int endHHMM: int - examMode: Optional[str] + examMode: str | None additionalInfo: dict[str, str] diff --git a/src/models/semester.py b/src/models/semester.py index b5b7bfa787c..0e1706c13f6 100644 --- a/src/models/semester.py +++ b/src/models/semester.py @@ -1,4 +1,5 @@ import datetime + from pydantic import BaseModel from .course import Course @@ -12,7 +13,9 @@ class Semester(BaseModel): endDate: int def __str__(self) -> str: - date_fmt = lambda ts: datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d") + def date_fmt(ts: int) -> str: + return datetime.datetime.fromtimestamp(ts).strftime("%Y-%m-%d") + start_date = date_fmt(self.startDate) end_date = date_fmt(self.endDate) return f"Semester(id={self.id}, name={self.name}, {start_date} - {end_date})" diff --git a/src/rss.py b/src/rss.py index f03abe76531..473a878e06a 100644 --- a/src/rss.py +++ b/src/rss.py @@ -1,11 +1,12 @@ +import datetime +from pathlib import Path +from typing import cast + +import feedgenerator import feedparser import html2text import yaml -from pathlib import Path from tqdm import tqdm -import feedgenerator -import datetime -from typing import cast from .utils.tj_rss import tj_ustc_RSS from .utils.tools import BUILD_DIR, RSS_CONFIG_PATH @@ -58,7 +59,7 @@ async def get_and_clean_feed(url: str, path_to_save: Path): async def make_rss() -> None: - with open(RSS_CONFIG_PATH, "r") as f: + with open(RSS_CONFIG_PATH) as f: config = yaml.safe_load(f) rss_path = BUILD_DIR / "rss" diff --git a/src/utils/auth.py b/src/utils/auth.py index ec5979fa104..c95cd39cf60 100644 --- a/src/utils/auth.py +++ b/src/utils/auth.py @@ -1,21 +1,21 @@ -import os import logging -from typing import Optional, Any, Dict -from pathlib import Path +import os from contextlib import suppress - -from pyotp import TOTP, parse_uri from enum import Enum +from pathlib import Path +from typing import Any + from dotenv import load_dotenv from patchright.async_api import ( - async_playwright, - Page, Browser, BrowserContext, + Page, Playwright, + async_playwright, ) +from pyotp import TOTP, parse_uri -from .tools import save_json, cache_dir_from_url +from .tools import cache_dir_from_url, save_json class RequestSession: @@ -33,7 +33,7 @@ def __init__( self.logger = logging.getLogger(__name__) async def get(self, url: str, **kwargs: Any): - params: Dict[str, Any] = { + params: dict[str, Any] = { "url": url, "timeout": kwargs.get("timeout", self.timeout_ms), "fail_on_status_code": kwargs.get( @@ -49,7 +49,7 @@ async def get(self, url: str, **kwargs: Any): return r async def post(self, url: str, data: Any = None, **kwargs: Any): - params: Dict[str, Any] = { + params: dict[str, Any] = { "url": url, "data": data, "timeout": kwargs.get("timeout", self.timeout_ms), @@ -87,14 +87,14 @@ async def post_json(self, url: str, data: Any = None, **kwargs: Any): class USTCSession: """Context manager for USTC authentication and returns a RequestSession""" - def __init__(self, headless: bool = True, proxy: Optional[dict] = None): + def __init__(self, headless: bool = True, proxy: dict | None = None): self.headless = headless self.proxy = proxy self.playwright: Playwright self.browser: Browser self.context: BrowserContext self.page: Page - self.totp: Optional[TOTP] = None + self.totp: TOTP | None = None self.logger = logging.getLogger(__name__) load_dotenv() @@ -103,7 +103,8 @@ def __init__(self, headless: bool = True, proxy: Optional[dict] = None): self.password = os.getenv("USTC_PASSPORT_PASSWORD", "") if self.username == "" or self.password == "": raise ValueError( - "USTC_PASSPORT_USERNAME and USTC_PASSPORT_PASSWORD must be set in environment variables" + "USTC_PASSPORT_USERNAME and USTC_PASSPORT_PASSWORD must be set in " + "environment variables" ) self.totp_url = os.getenv("USTC_PASSPORT_TOTP_URL", "") @@ -251,7 +252,7 @@ async def detect_login_state() -> LoginState: self.logger.error(f"login error={e}") return False - self.logger.info(f"login failed") + self.logger.info("login failed") return False async def _after_login(self): diff --git a/src/utils/catalog.py b/src/utils/catalog.py index a186eb9a19b..c1d90f4bef2 100644 --- a/src/utils/catalog.py +++ b/src/utils/catalog.py @@ -1,12 +1,12 @@ import asyncio -from ..models import Semester, Course, Exam +from ..models import Course, Exam, Semester +from .auth import RequestSession from .tools import ( - raw_date_to_unix_timestamp, compose_start_end, join_nonempty, + raw_date_to_unix_timestamp, ) -from .auth import RequestSession async def get_semesters(session: RequestSession) -> list[Semester]: diff --git a/src/utils/jw.py b/src/utils/jw.py index 8511ac64a20..2b08e520d1b 100644 --- a/src/utils/jw.py +++ b/src/utils/jw.py @@ -1,13 +1,12 @@ import logging from ..models import Course, Lecture +from .auth import RequestSession from .tools import ( cache_dir_from_url, compose_start_end, save_json, ) -from .auth import RequestSession - indexStartTimes: dict[int, int] = { 1: 7 * 60 + 50, @@ -55,9 +54,9 @@ def cleanLectures(lectures: list[Lecture]) -> list[Lecture]: for lecture in lectures: for r in result: if lecture.startDate >= r.startDate and lecture.endDate <= r.endDate: - if not lecture.teacherName in r.teacherName: + if lecture.teacherName not in r.teacherName: r.teacherName += "," + lecture.teacherName - if not lecture.location in r.location: + if lecture.location not in r.location: r.location += "," + lecture.location break elif lecture.endDate == r.startDate: diff --git a/src/utils/tj_rss.py b/src/utils/tj_rss.py index 0f16a55024a..5c0dc52156d 100644 --- a/src/utils/tj_rss.py +++ b/src/utils/tj_rss.py @@ -18,8 +18,14 @@ description = "体育教学中心,http://www.tj.ustc.edu.cn" headers = { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57", + "Accept": ( + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp," + "image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + ), + "user-agent": ( + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57" + ), } @@ -79,7 +85,7 @@ def tj_ustc_RSS(output_dir: Path | str): fg.description(description) fg.link(href="http://www.tj.ustc.edu.cn/tzgg/list.htm", rel="alternate") fg.language("zh-CN") - fg.lastBuildDate(datetime.datetime.utcnow().astimezone(tz=datetime.timezone.utc)) + fg.lastBuildDate(datetime.datetime.utcnow().astimezone(tz=datetime.UTC)) fg.ttl(5) for item in items: diff --git a/src/utils/tools.py b/src/utils/tools.py index af6d52a0d8a..98bea26f84e 100644 --- a/src/utils/tools.py +++ b/src/utils/tools.py @@ -1,10 +1,12 @@ +from collections.abc import Iterable from datetime import datetime from json import dump from pathlib import Path +from typing import Any +from urllib.parse import urlparse + from pydantic.json import pydantic_encoder from pytz import timezone -from typing import Any, Iterable, Tuple -from urllib.parse import urlparse tz = timezone("Asia/Shanghai") @@ -29,7 +31,7 @@ def save_json(obj: Any, path: Path) -> None: dump(obj, f, default=pydantic_encoder, ensure_ascii=False) -def compose_start_end(date_str: str, start_hhmm: int, end_hhmm: int) -> Tuple[int, int]: +def compose_start_end(date_str: str, start_hhmm: int, end_hhmm: int) -> tuple[int, int]: def compose_datetime(date_str: str, hhmm: int) -> int: base = raw_date_to_unix_timestamp(date_str) return base + int(hhmm // 100) * 3600 + int(hhmm % 100) * 60 @@ -48,7 +50,7 @@ def cache_dir_from_url(url: str) -> Path: "jw.ustc.edu.cn": "jw", } - if host in host_abbrs.keys(): + if host in host_abbrs: host = host_abbrs[host] return Path("build") / "cache" / host / path diff --git a/tools/bus_data_gen.py b/tools/bus_data_gen.py index f8f9e95aabb..8debaedb5cb 100644 --- a/tools/bus_data_gen.py +++ b/tools/bus_data_gen.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Optional + import jsonpickle @@ -34,14 +34,14 @@ def __init__(self, id: int, campuses: list[Campus]): class RouteSchedule: - def __init__(self, id: int, route: Route, time: list[list[Optional[str]]]): + def __init__(self, id: int, route: Route, time: list[list[str | None]]): self.id = id self.route = route self.time = time id: int route: Route - time: list[list[Optional[str]]] + time: list[list[str | None]] class RouteScheduleP: @@ -311,7 +311,7 @@ def __init__( ) -def generate_bus_data(output_path: Optional[Path] = None) -> Path: +def generate_bus_data(output_path: Path | None = None) -> Path: data_json = jsonpickle.encode(data, unpicklable=False) if output_path is None: output_path = ( diff --git a/uv.lock b/uv.lock index 0b37cb5cca2..3f9b077425a 100644 --- a/uv.lock +++ b/uv.lock @@ -408,6 +408,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/1c/53/f9c440463b3057485b8594d7a638bed53ba531165ef0ca0e6c364b5cc807/greenlet-3.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", size = 1564759, upload-time = "2025-11-04T12:42:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/47/e4/3bb4240abdd0a8d23f4f88adec746a3099f0d86bfedb623f063b2e3b4df0/greenlet-3.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", size = 1634288, upload-time = "2025-11-04T12:42:21.174Z" }, { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, @@ -415,6 +417,8 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/23/6e/74407aed965a4ab6ddd93a7ded3180b730d281c77b765788419484cdfeef/greenlet-3.2.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", size = 1612508, upload-time = "2025-11-04T12:42:23.427Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/343cd760ab2f92bac1845ca07ee3faea9fe52bee65f7bcb19f16ad7de08b/greenlet-3.2.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", size = 1680760, upload-time = "2025-11-04T12:42:25.341Z" }, { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, ] @@ -1632,6 +1636,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, ] +[[package]] +name = "ruff" +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +] + [[package]] name = "send2trash" version = "1.8.3" @@ -1725,6 +1755,7 @@ dev = [ { name = "jupyter" }, { name = "notebook" }, { name = "pandas" }, + { name = "ruff" }, ] [package.metadata] @@ -1752,6 +1783,7 @@ dev = [ { name = "jupyter", specifier = ">=1.1.1" }, { name = "notebook", specifier = ">=7.4.7" }, { name = "pandas", specifier = ">=2.3.3" }, + { name = "ruff", specifier = ">=0.14.14" }, ] [[package]] @@ -1870,11 +1902,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]]