From e8a61451566a03b6b036555d73419f8eb81e6481 Mon Sep 17 00:00:00 2001 From: FHU-yezi Date: Fri, 29 Nov 2024 10:33:17 +0800 Subject: [PATCH] =?UTF-8?q?style:=20=E4=BD=BF=E7=94=A8=E6=9B=B4=E5=A4=9A?= =?UTF-8?q?=20Ruff=20=E9=9D=99=E6=80=81=E6=A3=80=E6=9F=A5=E8=A7=84?= =?UTF-8?q?=E5=88=99=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.example.toml | 42 +++---- ...cle_earning_ranking_missing_author_slug.py | 2 +- jobs/jianshu/article_earning_ranking.py | 14 ++- jobs/jianshu/user_assets_ranking.py | 12 +- .../jianshu/article_earning_ranking_record.py | 19 +-- models/jianshu/user.py | 117 +++++++++--------- models/jianshu/user_assets_ranking_record.py | 21 ++-- models/jpep/credit_record.py | 19 +-- models/jpep/ftn_order.py | 15 ++- models/jpep/user.py | 71 ++++++----- pyproject.toml | 59 ++++++--- update_jianshu_user_data.py | 58 +++++++++ utils/prefect_helper.py | 10 +- uv.lock | 44 +++---- 14 files changed, 313 insertions(+), 190 deletions(-) create mode 100644 update_jianshu_user_data.py diff --git a/config.example.toml b/config.example.toml index dec110f..ae0af43 100644 --- a/config.example.toml +++ b/config.example.toml @@ -1,28 +1,28 @@ [jianshu_postgres] - host = "localhost" - port = 5432 - user = "postgres" - password = "postgres" - database = "jfetcher" +host = "localhost" +port = 5432 +user = "postgres" +password = "postgres" +database = "jfetcher" [jpep_postgres] - host = "localhost" - port = 5432 - user = "postgres" - password = "postgres" - database = "jpep" +host = "localhost" +port = 5432 +user = "postgres" +password = "postgres" +database = "jpep" [logging] - host = "localhost" - port = 5432 - user = "jfetcher" - password = "jfetcher" - table = "jfetcher" - display_level = "DEBUG" - save_level = "DEBUG" +host = "localhost" +port = 5432 +user = "jfetcher" +password = "jfetcher" +table = "jfetcher" +display_level = "DEBUG" +save_level = "DEBUG" [notify] - enabled = true - host = "localhost" - port = 8701 - token = "" \ No newline at end of file +enabled = true +host = "localhost" +port = 8701 +token = "" diff --git a/fix_article_earning_ranking_missing_author_slug.py b/fix_article_earning_ranking_missing_author_slug.py index 27661aa..ca5cd90 100644 --- a/fix_article_earning_ranking_missing_author_slug.py +++ b/fix_article_earning_ranking_missing_author_slug.py @@ -8,7 +8,7 @@ from utils.db import jianshu_pool -START_DATE = date(2024, 10, 30) +START_DATE = date(2024, 11, 26) CONFIG.data_validation.enabled = False logger = Logger() diff --git a/jobs/jianshu/article_earning_ranking.py b/jobs/jianshu/article_earning_ranking.py index e17c51c..37b22e2 100644 --- a/jobs/jianshu/article_earning_ranking.py +++ b/jobs/jianshu/article_earning_ranking.py @@ -1,10 +1,11 @@ +from __future__ import annotations + from datetime import date, timedelta -from typing import Optional +from typing import TYPE_CHECKING from jkit.config import CONFIG from jkit.exceptions import ResourceUnavailableError from jkit.ranking.article_earning import ArticleEarningRanking, RecordField -from jkit.user import UserInfo from prefect import flow from sshared.retry.asyncio import retry @@ -21,11 +22,14 @@ generate_flow_config, ) +if TYPE_CHECKING: + from jkit.user import UserInfo + @retry(attempts=3, delay=5) async def get_author_slug_and_info( item: RecordField, -) -> tuple[Optional[str], Optional[UserInfo]]: +) -> tuple[str | None, UserInfo | None]: flow_run_name = get_flow_run_name() if not item.slug: @@ -44,8 +48,6 @@ async def get_author_slug_and_info( author_info = await author.info CONFIG.data_validation.enabled = True - - return (author.slug, author_info) except ResourceUnavailableError: logger.warn( "文章或作者状态异常,跳过作者信息采集", @@ -53,6 +55,8 @@ async def get_author_slug_and_info( ranking=item.ranking, ) return None, None + else: + return (author.slug, author_info) async def process_item(item: RecordField, date_: date) -> ArticleEarningRankingRecord: diff --git a/jobs/jianshu/user_assets_ranking.py b/jobs/jianshu/user_assets_ranking.py index 4bdd746..18f41dc 100644 --- a/jobs/jianshu/user_assets_ranking.py +++ b/jobs/jianshu/user_assets_ranking.py @@ -1,5 +1,6 @@ +from __future__ import annotations + from datetime import date -from typing import Optional from jkit.config import CONFIG from jkit.exceptions import ResourceUnavailableError @@ -27,7 +28,7 @@ @retry(attempts=5, delay=10) async def get_fp_ftn_amount( item: AssetsRankingRecord, / -) -> tuple[Optional[float], Optional[float]]: +) -> tuple[float | None, float | None]: flow_run_name = get_flow_run_name() if not item.user_info.slug: @@ -46,8 +47,6 @@ async def get_fp_ftn_amount( fp_amount = await user_obj.fp_amount ftn_amount = abs(round(item.assets_amount - fp_amount, 3)) CONFIG.data_validation.enabled = True - - return fp_amount, ftn_amount except ResourceUnavailableError: logger.warn( "用户状态异常,跳过采集简书钻与简书贝信息", @@ -55,6 +54,8 @@ async def get_fp_ftn_amount( ranking=item.ranking, ) return None, None + else: + return fp_amount, ftn_amount async def process_item(item: AssetsRankingRecord, date_: date) -> DbAssetsRankingRecord: @@ -93,7 +94,8 @@ async def main() -> None: processed_item = await process_item(item, date_=date_) data.append(processed_item) - if len(data) == 1000: + # TODO + if len(data) == 1000: # noqa: PLR2004 break await DbAssetsRankingRecord.insert_many(data) diff --git a/models/jianshu/article_earning_ranking_record.py b/models/jianshu/article_earning_ranking_record.py index 46d10f2..6bdc552 100644 --- a/models/jianshu/article_earning_ranking_record.py +++ b/models/jianshu/article_earning_ranking_record.py @@ -1,24 +1,29 @@ -from datetime import date -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from sshared.postgres import Table -from sshared.strict_struct import NonEmptyStr, PositiveFloat, PositiveInt from utils.db import jianshu_pool +if TYPE_CHECKING: + from datetime import date + + from sshared.strict_struct import NonEmptyStr, PositiveFloat, PositiveInt + class ArticleEarningRankingRecord(Table, frozen=True): date: date ranking: PositiveInt - slug: Optional[NonEmptyStr] - title: Optional[NonEmptyStr] - author_slug: Optional[NonEmptyStr] + slug: NonEmptyStr | None + title: NonEmptyStr | None + author_slug: NonEmptyStr | None author_earning: PositiveFloat voter_earning: PositiveFloat @classmethod - async def insert_many(cls, data: list["ArticleEarningRankingRecord"]) -> None: + async def insert_many(cls, data: list[ArticleEarningRankingRecord]) -> None: for item in data: item.validate() diff --git a/models/jianshu/user.py b/models/jianshu/user.py index 4828c44..503dea6 100644 --- a/models/jianshu/user.py +++ b/models/jianshu/user.py @@ -1,12 +1,16 @@ +from __future__ import annotations + from datetime import datetime from enum import Enum -from typing import Optional +from typing import TYPE_CHECKING from sshared.postgres import Table -from sshared.strict_struct import NonEmptyStr, PositiveInt from utils.db import jianshu_pool +if TYPE_CHECKING: + from sshared.strict_struct import NonEmptyStr, PositiveInt + class StatusEnum(Enum): NORMAL = "NORMAL" @@ -17,10 +21,10 @@ class User(Table, frozen=True): slug: NonEmptyStr status: StatusEnum update_time: datetime - id: Optional[PositiveInt] - name: Optional[NonEmptyStr] + id: PositiveInt | None + name: NonEmptyStr | None history_names: list[NonEmptyStr] - avatar_url: Optional[NonEmptyStr] + avatar_url: NonEmptyStr | None async def create(self) -> None: self.validate() @@ -41,7 +45,7 @@ async def create(self) -> None: ) @classmethod - async def get_by_slug(cls, slug: str) -> Optional["User"]: + async def get_by_slug(cls, slug: str) -> User | None: async with jianshu_pool.get_conn() as conn: cursor = await conn.execute( "SELECT status, update_time, id, name, history_names, " @@ -67,9 +71,9 @@ async def get_by_slug(cls, slug: str) -> Optional["User"]: async def upsert( cls, slug: str, - id: Optional[int] = None, # noqa: A002 - name: Optional[str] = None, - avatar_url: Optional[str] = None, + id: int | None = None, # noqa: A002 + name: str | None = None, + avatar_url: str | None = None, ) -> None: user = await cls.get_by_slug(slug) # 如果不存在,创建用户 @@ -90,54 +94,53 @@ async def upsert( return # 在一个事务中一次性完成全部字段的更新 - async with jianshu_pool.get_conn() as conn: # noqa: SIM117 - async with conn.transaction(): - # 更新更新时间 + async with jianshu_pool.get_conn() as conn, conn.transaction(): + # 更新更新时间 + await conn.execute( + "UPDATE users SET update_time = %s WHERE slug = %s;", + (datetime.now(), slug), + ) + + # ID 无法被修改,如果异常则抛出错误 + if user.id and id and user.id != id: + raise ValueError(f"用户 ID 不一致:{user.id} != {id}") + + # 如果没有存储 ID,进行添加 + if not user.id and id: await conn.execute( - "UPDATE users SET update_time = %s WHERE slug = %s;", - (datetime.now(), slug), + "UPDATE users SET id = %s WHERE slug = %s;", + (id, slug), ) - # ID 无法被修改,如果异常则抛出错误 - if user.id and id and user.id != id: - raise ValueError(f"用户 ID 不一致:{user.id} != {id}") - - # 如果没有存储 ID,进行添加 - if not user.id and id: - await conn.execute( - "UPDATE users SET id = %s WHERE slug = %s;", - (id, slug), - ) - - # 如果没有存储昵称,进行添加 - if not user.name and name: - await conn.execute( - "UPDATE users SET name = %s WHERE slug = %s;", - (name, slug), - ) - - # 更新昵称 - if user.name and name and user.name != name: - await conn.execute( - "UPDATE users SET name = %s WHERE slug = %s;", - (name, slug), - ) - await conn.execute( - "UPDATE users SET history_names = array_append(history_names, " - "%s) WHERE slug = %s;", - (user.name, slug), - ) - - # 如果没有存储头像链接,进行添加 - if not user.avatar_url and avatar_url: - await conn.execute( - "UPDATE users SET avatar_url = %s WHERE slug = %s;", - (avatar_url, slug), - ) - - # 更新头像链接 - if user.avatar_url and avatar_url and user.avatar_url != avatar_url: - await conn.execute( - "UPDATE users SET avatar_url = %s WHERE slug = %s;", - (avatar_url, slug), - ) + # 如果没有存储昵称,进行添加 + if not user.name and name: + await conn.execute( + "UPDATE users SET name = %s WHERE slug = %s;", + (name, slug), + ) + + # 更新昵称 + if user.name and name and user.name != name: + await conn.execute( + "UPDATE users SET name = %s WHERE slug = %s;", + (name, slug), + ) + await conn.execute( + "UPDATE users SET history_names = array_append(history_names, " + "%s) WHERE slug = %s;", + (user.name, slug), + ) + + # 如果没有存储头像链接,进行添加 + if not user.avatar_url and avatar_url: + await conn.execute( + "UPDATE users SET avatar_url = %s WHERE slug = %s;", + (avatar_url, slug), + ) + + # 更新头像链接 + if user.avatar_url and avatar_url and user.avatar_url != avatar_url: + await conn.execute( + "UPDATE users SET avatar_url = %s WHERE slug = %s;", + (avatar_url, slug), + ) diff --git a/models/jianshu/user_assets_ranking_record.py b/models/jianshu/user_assets_ranking_record.py index 17ab2a6..6c9f9ad 100644 --- a/models/jianshu/user_assets_ranking_record.py +++ b/models/jianshu/user_assets_ranking_record.py @@ -1,23 +1,28 @@ -from datetime import date -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from sshared.postgres import Table -from sshared.strict_struct import NonEmptyStr, NonNegativeFloat, PositiveInt from utils.db import jianshu_pool +if TYPE_CHECKING: + from datetime import date + + from sshared.strict_struct import NonEmptyStr, NonNegativeFloat, PositiveInt + class UserAssetsRankingRecord(Table, frozen=True): date: date ranking: PositiveInt - slug: Optional[NonEmptyStr] + slug: NonEmptyStr | None - fp: Optional[NonNegativeFloat] - ftn: Optional[NonNegativeFloat] - assets: Optional[NonNegativeFloat] + fp: NonNegativeFloat | None + ftn: NonNegativeFloat | None + assets: NonNegativeFloat | None @classmethod - async def insert_many(cls, data: list["UserAssetsRankingRecord"]) -> None: + async def insert_many(cls, data: list[UserAssetsRankingRecord]) -> None: for item in data: item.validate() diff --git a/models/jpep/credit_record.py b/models/jpep/credit_record.py index 2ce8a65..b814435 100644 --- a/models/jpep/credit_record.py +++ b/models/jpep/credit_record.py @@ -1,14 +1,19 @@ -from datetime import datetime -from typing import Optional +from __future__ import annotations + +from typing import TYPE_CHECKING from sshared.postgres import Table -from sshared.strict_struct import ( - NonNegativeInt, - PositiveInt, -) from utils.db import jpep_pool +if TYPE_CHECKING: + from datetime import datetime + + from sshared.strict_struct import ( + NonNegativeInt, + PositiveInt, + ) + class CreditRecord(Table, frozen=True): time: datetime @@ -26,7 +31,7 @@ async def create(self) -> None: ) @classmethod - async def get_latest_credit(cls, user_id: int) -> Optional[int]: + async def get_latest_credit(cls, user_id: int) -> int | None: async with jpep_pool.get_conn() as conn: cursor = await conn.execute( "SELECT credit FROM credit_records WHERE user_id = %s " diff --git a/models/jpep/ftn_order.py b/models/jpep/ftn_order.py index be7d25a..1777239 100644 --- a/models/jpep/ftn_order.py +++ b/models/jpep/ftn_order.py @@ -1,12 +1,17 @@ -from datetime import datetime +from __future__ import annotations + from enum import Enum -from typing import Optional +from typing import TYPE_CHECKING from sshared.postgres import Table -from sshared.strict_struct import PositiveInt from utils.db import jpep_pool +if TYPE_CHECKING: + from datetime import datetime + + from sshared.strict_struct import PositiveInt + class TypeEnum(Enum): BUY = "BUY" @@ -18,7 +23,7 @@ class FTNOrder(Table, frozen=True): type: TypeEnum publisher_id: PositiveInt publish_time: datetime - last_seen_time: Optional[datetime] + last_seen_time: datetime | None async def create(self) -> None: async with jpep_pool.get_conn() as conn: @@ -35,7 +40,7 @@ async def create(self) -> None: ) @classmethod - async def get_by_id(cls, id: int) -> Optional["FTNOrder"]: # noqa: A002 + async def get_by_id(cls, id: int) -> FTNOrder | None: # noqa: A002 async with jpep_pool.get_conn() as conn: cursor = await conn.execute( "SELECT type, publisher_id, publish_time, last_seen_time " diff --git a/models/jpep/user.py b/models/jpep/user.py index 4088ef5..236d2d8 100644 --- a/models/jpep/user.py +++ b/models/jpep/user.py @@ -1,21 +1,25 @@ +from __future__ import annotations + from datetime import datetime -from typing import Optional +from typing import TYPE_CHECKING from sshared.postgres import Table -from sshared.strict_struct import ( - NonEmptyStr, - PositiveInt, -) from utils.db import jpep_pool +if TYPE_CHECKING: + from sshared.strict_struct import ( + NonEmptyStr, + PositiveInt, + ) + class User(Table, frozen=True): id: PositiveInt update_time: datetime name: NonEmptyStr hashed_name: NonEmptyStr - avatar_url: Optional[NonEmptyStr] + avatar_url: NonEmptyStr | None async def create(self) -> None: self.validate() @@ -34,7 +38,7 @@ async def create(self) -> None: ) @classmethod - async def get_by_id(cls, id: int) -> Optional["User"]: # noqa: A002 + async def get_by_id(cls, id: int) -> User | None: # noqa: A002 async with jpep_pool.get_conn() as conn: cursor = await conn.execute( "SELECT update_time, name, hashed_name, avatar_url " @@ -60,7 +64,7 @@ async def upsert( id: int, # noqa: A002 name: str, hashed_name: str, - avatar_url: Optional[str] = None, + avatar_url: str | None = None, ) -> None: user = await cls.get_by_id(id) # 如果不存在,创建用户 @@ -79,32 +83,31 @@ async def upsert( return # 在一个事务中一次性完成全部字段的更新 - async with jpep_pool.get_conn() as conn: # noqa: SIM117 - async with conn.transaction(): - # 更新更新时间 + async with jpep_pool.get_conn() as conn, conn.transaction(): + # 更新更新时间 + await conn.execute( + "UPDATE users SET update_time = %s WHERE id = %s;", + (datetime.now(), id), + ) + + # 更新昵称和哈希后昵称 + if user.name and name and user.name != name: + # 哈希后昵称一定会跟随昵称变化,一同更新 await conn.execute( - "UPDATE users SET update_time = %s WHERE id = %s;", - (datetime.now(), id), + "UPDATE users SET name = %s, hashed_name = %s WHERE id = %s;", + (name, hashed_name, id), ) - # 更新昵称和哈希后昵称 - if user.name and name and user.name != name: - # 哈希后昵称一定会跟随昵称变化,一同更新 - await conn.execute( - "UPDATE users SET name = %s, hashed_name = %s WHERE id = %s;", - (name, hashed_name, id), - ) - - # 如果没有存储头像链接,进行添加 - if not user.avatar_url and avatar_url: - await conn.execute( - "UPDATE users SET avatar_url = %s WHERE id = %s;", - (avatar_url, id), - ) - - # 更新头像链接 - if user.avatar_url and avatar_url and user.avatar_url != avatar_url: - await conn.execute( - "UPDATE users SET avatar_url = %s WHERE id = %s;", - (avatar_url, id), - ) + # 如果没有存储头像链接,进行添加 + if not user.avatar_url and avatar_url: + await conn.execute( + "UPDATE users SET avatar_url = %s WHERE id = %s;", + (avatar_url, id), + ) + + # 更新头像链接 + if user.avatar_url and avatar_url and user.avatar_url != avatar_url: + await conn.execute( + "UPDATE users SET avatar_url = %s WHERE id = %s;", + (avatar_url, id), + ) diff --git a/pyproject.toml b/pyproject.toml index b9f391b..839b9f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,10 +3,8 @@ name = "jfetcher" version = "3.10.0" description = "简书数据采集工具" readme = "README.md" -license = {file = "LICENSE"} -authors = [ - {name = "FHU-yezi", email = "yehaowei20060411@qq.com"}, -] +license = { file = "LICENSE" } +authors = [{ name = "FHU-yezi", email = "yehaowei20060411@qq.com" }] requires-python = ">=3.9" dependencies = [ "jkit>=3.0.0a16", @@ -16,19 +14,50 @@ dependencies = [ ] [tool.uv] -dev-dependencies = [ - "pyright>=1.1.0", - "ruff>=0.7.0", -] +dev-dependencies = ["pyright>=1.1.0", "ruff>=0.8.0"] [tool.ruff] target-version = "py39" lint.select = [ - "A", "ANN", "ARG", "ASYNC", "B", - "BLE", "C4", "E", "F", "I", - "ICN", "ISC", "N", "PERF", "PIE", - "PT", "Q", "RET", "RSE", "RUF", - "S", "SIM", "SLOT", "TCH", "UP", - "W" + "A", + "ANN", + "ARG", + "ASYNC", + "B", + "C4", + "E", + "F", + "FA", + "FBT", + "FURB", + "I", + "ISC", + "N", + "PERF", + "PIE", + "PL", + "PT", + "PTH", + "Q", + "RET", + "RSE", + "RUF", + "S", + "SIM", + "SLF", + "T20", + "TC", + "TID", + "TRY", + "UP", + "W", +] +lint.ignore = [ + "ISC001", + "PERF203", + "RUF001", + "RUF002", + "RUF003", + "S311", + "TRY003", ] -lint.ignore = ["ANN101", "ANN102", "ISC001", "RUF001", "RUF002", "RUF003"] diff --git a/update_jianshu_user_data.py b/update_jianshu_user_data.py new file mode 100644 index 0000000..852e978 --- /dev/null +++ b/update_jianshu_user_data.py @@ -0,0 +1,58 @@ +from asyncio import run as asyncio_run + +from jkit.config import CONFIG +from jkit.exceptions import ResourceUnavailableError +from jkit.user import User +from sshared.logging import Logger +from sshared.time import without_microsecond + +from models.jianshu.user import User as DbUser +from utils.db import jianshu_pool + +LIMIT = 50 + +CONFIG.data_validation.enabled = False +logger = Logger() + + +async def set_user_status_inaccessible(slug: str) -> None: + async with jianshu_pool.get_conn() as conn: + await conn.execute( + "UPDATE users SET status = 'INACCESSIBLE' WHERE slug = %s;", (slug,) + ) + + +async def main() -> None: + async with jianshu_pool.get_conn() as conn: + cursor = await conn.execute( + "SELECT slug, update_time FROM users ORDER BY update_time LIMIT %s;", + (LIMIT,), + ) + async for item in cursor: + slug, update_time = item + user = User.from_slug(slug) + try: + await user.check() + except ResourceUnavailableError: + await set_user_status_inaccessible(slug) + logger.warn( + f"用户 {slug} 不存在或已注销 / 被封禁,已将其状态设为不可访问" + ) + continue + + user_info = await user.info + + await DbUser.upsert( + slug=slug, + id=user_info.id, + name=user_info.name, + avatar_url=user_info.avatar_url, + ) + + logger.debug( + f"已更新 {slug} 的用户信息" + f"(上次更新时间:{without_microsecond(update_time)})" + ) + + +asyncio_run(main()) diff --git a/utils/prefect_helper.py b/utils/prefect_helper.py index 30b8943..4970d37 100644 --- a/utils/prefect_helper.py +++ b/utils/prefect_helper.py @@ -1,13 +1,17 @@ +from __future__ import annotations + from functools import partial -from typing import Any, Literal, Union +from typing import TYPE_CHECKING, Any, Literal -from prefect.client.schemas.objects import Flow, FlowRun, State from prefect.client.schemas.schedules import CronSchedule from sshared.notifier import Notifier from utils.config import CONFIG from utils.log import logger +if TYPE_CHECKING: + from prefect.client.schemas.objects import Flow, FlowRun, State + if CONFIG.notify.enabled: notifier = Notifier( host=CONFIG.notify.host, port=CONFIG.notify.port, token=CONFIG.notify.token @@ -53,7 +57,7 @@ async def failed_flow_run_handler( def generate_flow_config( name: str, retries: int = 1, - retry_delay_seconds: Union[int, float] = 5, + retry_delay_seconds: int | float = 5, timeout: int = 3600, ) -> dict[str, Any]: return { diff --git a/uv.lock b/uv.lock index f6f6518..52d2a35 100644 --- a/uv.lock +++ b/uv.lock @@ -733,7 +733,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ { name = "pyright", specifier = ">=1.1.0" }, - { name = "ruff", specifier = ">=0.7.0" }, + { name = "ruff", specifier = ">=0.8.0" }, ] [[package]] @@ -1929,27 +1929,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/06/09d1276df977eece383d0ed66052fc24ec4550a61f8fbc0a11200e690496/ruff-0.7.3.tar.gz", hash = "sha256:e1d1ba2e40b6e71a61b063354d04be669ab0d39c352461f3d789cac68b54a313", size = 3243664 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/56/933d433c2489e4642487b835f53dd9ff015fb3d8fa459b09bb2ce42d7c4b/ruff-0.7.3-py3-none-linux_armv6l.whl", hash = "sha256:34f2339dc22687ec7e7002792d1f50712bf84a13d5152e75712ac08be565d344", size = 10372090 }, - { url = "https://files.pythonhosted.org/packages/20/ea/1f0a22a6bcdd3fc26c73f63a025d05bd565901b729d56bcb093c722a6c4c/ruff-0.7.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fb397332a1879b9764a3455a0bb1087bda876c2db8aca3a3cbb67b3dbce8cda0", size = 10190037 }, - { url = "https://files.pythonhosted.org/packages/16/74/aca75666e0d481fe394e76a8647c44ea919087748024924baa1a17371e3e/ruff-0.7.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:37d0b619546103274e7f62643d14e1adcbccb242efda4e4bdb9544d7764782e9", size = 9811998 }, - { url = "https://files.pythonhosted.org/packages/20/a1/cf446a0d7f78ea1f0bd2b9171c11dfe746585c0c4a734b25966121eb4f5d/ruff-0.7.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59f0c3ee4d1a6787614e7135b72e21024875266101142a09a61439cb6e38a5", size = 10620626 }, - { url = "https://files.pythonhosted.org/packages/cd/c1/82b27d09286ae855f5d03b1ad37cf243f21eb0081732d4d7b0d658d439cb/ruff-0.7.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44eb93c2499a169d49fafd07bc62ac89b1bc800b197e50ff4633aed212569299", size = 10177598 }, - { url = "https://files.pythonhosted.org/packages/b9/42/c0acac22753bf74013d035a5ef6c5c4c40ad4d6686bfb3fda7c6f37d9b37/ruff-0.7.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d0242ce53f3a576c35ee32d907475a8d569944c0407f91d207c8af5be5dae4e", size = 11171963 }, - { url = "https://files.pythonhosted.org/packages/43/18/bb0befb7fb9121dd9009e6a72eb98e24f1bacb07c6f3ecb55f032ba98aed/ruff-0.7.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6b6224af8b5e09772c2ecb8dc9f3f344c1aa48201c7f07e7315367f6dd90ac29", size = 11856157 }, - { url = "https://files.pythonhosted.org/packages/5e/91/04e98d7d6e32eca9d1372be595f9abc7b7f048795e32eb2edbd8794d50bd/ruff-0.7.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c50f95a82b94421c964fae4c27c0242890a20fe67d203d127e84fbb8013855f5", size = 11440331 }, - { url = "https://files.pythonhosted.org/packages/f5/dc/3fe99f2ce10b76d389041a1b9f99e7066332e479435d4bebcceea16caff5/ruff-0.7.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f3eff9961b5d2644bcf1616c606e93baa2d6b349e8aa8b035f654df252c8c67", size = 12725354 }, - { url = "https://files.pythonhosted.org/packages/43/7b/1daa712de1c5bc6cbbf9fa60e9c41cc48cda962dc6d2c4f2a224d2c3007e/ruff-0.7.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8963cab06d130c4df2fd52c84e9f10d297826d2e8169ae0c798b6221be1d1d2", size = 11010091 }, - { url = "https://files.pythonhosted.org/packages/b6/db/1227a903587432eb569e57a95b15a4f191a71fe315cde4c0312df7bc85da/ruff-0.7.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:61b46049d6edc0e4317fb14b33bd693245281a3007288b68a3f5b74a22a0746d", size = 10610687 }, - { url = "https://files.pythonhosted.org/packages/db/e2/dc41ee90c3085aadad4da614d310d834f641aaafddf3dfbba08210c616ce/ruff-0.7.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:10ebce7696afe4644e8c1a23b3cf8c0f2193a310c18387c06e583ae9ef284de2", size = 10254843 }, - { url = "https://files.pythonhosted.org/packages/6f/09/5f6cac1c91542bc5bd33d40b4c13b637bf64d7bb29e091dadb01b62527fe/ruff-0.7.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3f36d56326b3aef8eeee150b700e519880d1aab92f471eefdef656fd57492aa2", size = 10730962 }, - { url = "https://files.pythonhosted.org/packages/d3/42/89a4b9a24ef7d00269e24086c417a006f9a3ffeac2c80f2629eb5ce140ee/ruff-0.7.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5d024301109a0007b78d57ab0ba190087b43dce852e552734ebf0b0b85e4fb16", size = 11101907 }, - { url = "https://files.pythonhosted.org/packages/b0/5c/efdb4777686683a8edce94ffd812783bddcd3d2454d38c5ac193fef7c500/ruff-0.7.3-py3-none-win32.whl", hash = "sha256:4ba81a5f0c5478aa61674c5a2194de8b02652f17addf8dfc40c8937e6e7d79fc", size = 8611095 }, - { url = "https://files.pythonhosted.org/packages/bb/b8/28fbc6a4efa50178f973972d1c84b2d0a33cdc731588522ab751ac3da2f5/ruff-0.7.3-py3-none-win_amd64.whl", hash = "sha256:588a9ff2fecf01025ed065fe28809cd5a53b43505f48b69a1ac7707b1b7e4088", size = 9418283 }, - { url = "https://files.pythonhosted.org/packages/3f/77/b587cba6febd5e2003374f37eb89633f79f161e71084f94057c8653b7fb3/ruff-0.7.3-py3-none-win_arm64.whl", hash = "sha256:1713e2c5545863cdbfe2cbce21f69ffaf37b813bfd1fb3b90dc9a6f1963f5a8c", size = 8725228 }, +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/d6/a2373f3ba7180ddb44420d2a9d1f1510e1a4d162b3d27282bedcb09c8da9/ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44", size = 3276537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/77/e889ee3ce7fd8baa3ed1b77a03b9fb8ec1be68be1418261522fd6a5405e0/ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea", size = 10518283 }, + { url = "https://files.pythonhosted.org/packages/da/c8/0a47de01edf19fb22f5f9b7964f46a68d0bdff20144d134556ffd1ba9154/ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b", size = 10317691 }, + { url = "https://files.pythonhosted.org/packages/41/17/9885e4a0eeae07abd2a4ebabc3246f556719f24efa477ba2739146c4635a/ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a", size = 9940999 }, + { url = "https://files.pythonhosted.org/packages/3e/cd/46b6f7043597eb318b5f5482c8ae8f5491cccce771e85f59d23106f2d179/ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99", size = 10772437 }, + { url = "https://files.pythonhosted.org/packages/5d/87/afc95aeb8bc78b1d8a3461717a4419c05aa8aa943d4c9cbd441630f85584/ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c", size = 10299156 }, + { url = "https://files.pythonhosted.org/packages/65/fa/04c647bb809c4d65e8eae1ed1c654d9481b21dd942e743cd33511687b9f9/ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9", size = 11325819 }, + { url = "https://files.pythonhosted.org/packages/90/26/7dad6e7d833d391a8a1afe4ee70ca6f36c4a297d3cca83ef10e83e9aacf3/ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362", size = 12023927 }, + { url = "https://files.pythonhosted.org/packages/24/a0/be5296dda6428ba8a13bda8d09fbc0e14c810b485478733886e61597ae2b/ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df", size = 11589702 }, + { url = "https://files.pythonhosted.org/packages/26/3f/7602eb11d2886db545834182a9dbe500b8211fcbc9b4064bf9d358bbbbb4/ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3", size = 12782936 }, + { url = "https://files.pythonhosted.org/packages/4c/5d/083181bdec4ec92a431c1291d3fff65eef3ded630a4b55eb735000ef5f3b/ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c", size = 11138488 }, + { url = "https://files.pythonhosted.org/packages/b7/23/c12cdef58413cee2436d6a177aa06f7a366ebbca916cf10820706f632459/ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2", size = 10744474 }, + { url = "https://files.pythonhosted.org/packages/29/61/a12f3b81520083cd7c5caa24ba61bb99fd1060256482eff0ef04cc5ccd1b/ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70", size = 10369029 }, + { url = "https://files.pythonhosted.org/packages/08/2a/c013f4f3e4a54596c369cee74c24870ed1d534f31a35504908b1fc97017a/ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd", size = 10867481 }, + { url = "https://files.pythonhosted.org/packages/d5/f7/685b1e1d42a3e94ceb25eab23c70bdd8c0ab66a43121ef83fe6db5a58756/ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426", size = 11237117 }, + { url = "https://files.pythonhosted.org/packages/03/20/401132c0908e8837625e3b7e32df9962e7cd681a4df1e16a10e2a5b4ecda/ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468", size = 8783511 }, + { url = "https://files.pythonhosted.org/packages/1d/5c/4d800fca7854f62ad77f2c0d99b4b585f03e2d87a6ec1ecea85543a14a3c/ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f", size = 9559876 }, + { url = "https://files.pythonhosted.org/packages/5b/bc/cc8a6a5ca4960b226dc15dd8fb511dd11f2014ff89d325c0b9b9faa9871f/ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6", size = 8939733 }, ] [[package]]