replace black/isort/pylint with ruff

Этот коммит содержится в:
Vlad Pronsky 2024-01-05 04:50:56 +02:00
родитель c1e28cbfb4
Коммит 4b67be62bb
9 изменённых файлов: 77 добавлений и 108 удалений

16
.vscode/settings.json поставляемый
Просмотреть файл

@ -1,14 +1,14 @@
{ {
"files.exclude": {
".ruff_cache": true,
".pytest_cache": true,
"*.egg-info": true,
"build": true,
".coverage": true,
},
"[python]": { "[python]": {
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": ["source.organizeImports"], "editor.codeActionsOnSave": ["source.organizeImports"],
"editor.defaultFormatter": "ms-python.black-formatter" "editor.defaultFormatter": "charliermarsh.ruff"
},
"files.exclude": {
".ruff_cache": true,
".pytest_cache": true,
".coverage": true,
"htmlcov": true,
"dist": true
}, },
} }

Просмотреть файл

@ -7,24 +7,11 @@ install:
build: build:
@python -m build @python -m build
ci:
@make format
@make lint
@make test
format:
@black .
lint: lint:
@ruff check twscrape # https://docs.astral.sh/ruff/settings/#sorting-imports
@ruff check tests @ruff check --select I --fix .
@ruff format .
lint-fix: @ruff check .
@ruff check --fix twscrape
@ruff check --fix tests
pylint:
@pylint --errors-only twscrape
test: test:
@pytest -s --cov=twscrape tests/ @pytest -s --cov=twscrape tests/

Просмотреть файл

@ -2,9 +2,6 @@
requires = ["hatchling>=1.9.1"] requires = ["hatchling>=1.9.1"]
build-backend = "hatchling.build" build-backend = "hatchling.build"
[tool.hatch.build.targets.wheel]
packages = ["twscrape"]
[project] [project]
name = "twscrape" name = "twscrape"
version = "0.9.0" version = "0.9.0"
@ -30,12 +27,11 @@ dependencies = [
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"pylint>=2.17.3", "pytest-asyncio>=0.23.3",
"pytest-asyncio>=0.21.0", "pytest-cov>=4.1.0",
"pytest-cov>=4.0.0", "pytest-httpx>=0.28.0",
"pytest-httpx>=0.22.0", "pytest>=7.4.4",
"pytest>=7.4.0", "ruff>=0.1.11"
"ruff"
] ]
[project.urls] [project.urls]
@ -47,30 +43,20 @@ twscrape = "twscrape.cli:run"
[tool.setuptools] [tool.setuptools]
packages = ['twscrape'] packages = ['twscrape']
[tool.pylint] [tool.hatch.build.targets.wheel]
max-line-length = 99 packages = ["twscrape"]
disable = [
"C0103", # invalid-name [tool.hatch.metadata]
"C0114", # missing-module-docstring allow-direct-references = true
"C0115", # missing-class-docstring
"C0116", # missing-function-docstring
"R0903", # too-few-public-methods
"R0913", # too-many-arguments
"W0105", # pointless-string-statement
]
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = ["."] pythonpath = ["."]
asyncio_mode = "auto" asyncio_mode = "auto"
[tool.isort]
profile = "black"
[tool.black]
line-length = 99
[tool.ruff] [tool.ruff]
line-length = 99 line-length = 99
[tool.hatch.metadata] [tool.ruff.lint]
allow-direct-references = true ignore = ["E501"]
[tool.ruff.format]

Просмотреть файл

@ -1,4 +1,5 @@
# ruff: noqa: E501 import pytest
from twscrape.utils import parse_cookies from twscrape.utils import parse_cookies
@ -17,3 +18,10 @@ def test_cookies_parse():
val = "W3sibmFtZSI6ICJhYmMiLCAidmFsdWUiOiAiMTIzIn0sIHsibmFtZSI6ICJkZWYiLCAidmFsdWUiOiAiNDU2In0sIHsibmFtZSI6ICJnaGkiLCAidmFsdWUiOiAiNzg5In1d" val = "W3sibmFtZSI6ICJhYmMiLCAidmFsdWUiOiAiMTIzIn0sIHsibmFtZSI6ICJkZWYiLCAidmFsdWUiOiAiNDU2In0sIHsibmFtZSI6ICJnaGkiLCAidmFsdWUiOiAiNzg5In1d"
assert parse_cookies(val) == {"abc": "123", "def": "456", "ghi": "789"} assert parse_cookies(val) == {"abc": "123", "def": "456", "ghi": "789"}
val = '{"cookies": {"abc": "123", "def": "456", "ghi": "789"}}'
assert parse_cookies(val) == {"abc": "123", "def": "456", "ghi": "789"}
with pytest.raises(ValueError, match=r"Invalid cookie value: .+"):
val = "{invalid}"
assert parse_cookies(val) == {}

Просмотреть файл

@ -5,10 +5,11 @@ from datetime import datetime
from httpx import AsyncClient, AsyncHTTPTransport from httpx import AsyncClient, AsyncHTTPTransport
from .constants import TOKEN
from .models import JSONTrait from .models import JSONTrait
from .utils import utc from .utils import utc
TOKEN = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"
@dataclass @dataclass
class Account(JSONTrait): class Account(JSONTrait):

Просмотреть файл

@ -1,4 +1,3 @@
# ruff: noqa: E501
import asyncio import asyncio
import sqlite3 import sqlite3
import uuid import uuid

Просмотреть файл

@ -1,17 +1,52 @@
# ruff: noqa: F405
from httpx import Response from httpx import Response
from .accounts_pool import AccountsPool from .accounts_pool import AccountsPool
from .constants import * # noqa: F403
from .logger import set_log_level from .logger import set_log_level
from .models import Tweet, User, parse_tweet, parse_tweets, parse_user, parse_users from .models import Tweet, User, parse_tweet, parse_tweets, parse_user, parse_users
from .queue_client import QueueClient from .queue_client import QueueClient
from .utils import encode_params, find_obj, get_by_path from .utils import encode_params, find_obj, get_by_path
# Note: kv is variables, ft is features from original GQL request OP_SearchTimeline = "Aj1nGkALq99Xg3XI0OZBtw/SearchTimeline"
OP_UserByRestId = "CO4_gU4G_MRREoqfiTh6Hg/UserByRestId"
OP_UserByScreenName = "NimuplG1OB7Fd2btCLdBOw/UserByScreenName"
OP_TweetDetail = "-H4B_lJDEA-O_7_qWaRiyg/TweetDetail"
OP_Followers = "3_7xfjmh897x8h_n6QBqTA/Followers"
OP_Following = "0yD6Eiv23DKXRDU9VxlG2A/Following"
OP_Retweeters = "sOBhVzDeJl4XGepvi5pHlg/Retweeters"
OP_Favoriters = "E-ZTxvWWIkmOKwYdNTEefg/Favoriters"
OP_UserTweets = "V1ze5q3ijDS1VeLwLY0m7g/UserTweets"
OP_UserTweetsAndReplies = "16nOjYqEdV04vN6-rgg8KA/UserTweetsAndReplies"
OP_ListLatestTweetsTimeline = "whF0_KH1fCkdLLoyNPMoEw/ListLatestTweetsTimeline"
GQL_URL = "https://twitter.com/i/api/graphql"
GQL_FEATURES = { # search values here (view source) https://twitter.com/
"responsive_web_graphql_exclude_directive_enabled": True,
"verified_phone_label_enabled": False,
"responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
"responsive_web_graphql_timeline_navigation_enabled": True,
"tweetypie_unmention_optimization_enabled": True,
"responsive_web_edit_tweet_api_enabled": True,
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
"view_counts_everywhere_api_enabled": True,
"longform_notetweets_consumption_enabled": True,
"tweet_awards_web_tipping_enabled": False,
"freedom_of_speech_not_reach_fetch_enabled": True,
"standardized_nudges_misinfo": True,
"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": True,
"longform_notetweets_rich_text_read_enabled": True,
"responsive_web_enhance_cards_enabled": False,
"creator_subscriptions_tweet_preview_api_enabled": True,
"longform_notetweets_inline_media_enabled": True,
"responsive_web_media_download_video_enabled": False,
"responsive_web_twitter_article_tweet_consumption_enabled": False,
"c9s_tweet_anatomy_moderator_badge_enabled": True,
"rweb_video_timestamps_enabled": True,
}
class API: class API:
# Note: kv is variables, ft is features from original GQL request
pool: AccountsPool pool: AccountsPool
def __init__(self, pool: AccountsPool | str | None = None, debug=False): def __init__(self, pool: AccountsPool | str | None = None, debug=False):

Просмотреть файл

@ -1,48 +0,0 @@
TOKEN = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" # noqa: E501
GQL_URL = "https://twitter.com/i/api/graphql"
LOGIN_URL = "https://api.twitter.com/1.1/onboarding/task.json"
OP_SearchTimeline = "Aj1nGkALq99Xg3XI0OZBtw/SearchTimeline"
OP_UserByRestId = "CO4_gU4G_MRREoqfiTh6Hg/UserByRestId"
OP_UserByScreenName = "NimuplG1OB7Fd2btCLdBOw/UserByScreenName"
OP_TweetDetail = "-H4B_lJDEA-O_7_qWaRiyg/TweetDetail"
OP_Followers = "3_7xfjmh897x8h_n6QBqTA/Followers"
OP_Following = "0yD6Eiv23DKXRDU9VxlG2A/Following"
OP_Retweeters = "sOBhVzDeJl4XGepvi5pHlg/Retweeters"
OP_Favoriters = "E-ZTxvWWIkmOKwYdNTEefg/Favoriters"
OP_UserTweets = "V1ze5q3ijDS1VeLwLY0m7g/UserTweets"
OP_UserTweetsAndReplies = "16nOjYqEdV04vN6-rgg8KA/UserTweetsAndReplies"
OP_ListLatestTweetsTimeline = "whF0_KH1fCkdLLoyNPMoEw/ListLatestTweetsTimeline"
# search values here (view source) https://twitter.com/
GQL_FEATURES = {
# "blue_business_profile_image_shape_enabled": True,
"responsive_web_graphql_exclude_directive_enabled": True,
"verified_phone_label_enabled": False,
"responsive_web_graphql_skip_user_profile_image_extensions_enabled": False,
"responsive_web_graphql_timeline_navigation_enabled": True,
"tweetypie_unmention_optimization_enabled": True,
# "vibe_api_enabled": True,
"responsive_web_edit_tweet_api_enabled": True,
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": True,
"view_counts_everywhere_api_enabled": True,
"longform_notetweets_consumption_enabled": True,
"tweet_awards_web_tipping_enabled": False,
"freedom_of_speech_not_reach_fetch_enabled": True,
"standardized_nudges_misinfo": True,
"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": True,
# "interactive_text_enabled": True,
# "responsive_web_text_conversations_enabled": False,
"longform_notetweets_rich_text_read_enabled": True,
"responsive_web_enhance_cards_enabled": False,
"creator_subscriptions_tweet_preview_api_enabled": True,
"longform_notetweets_inline_media_enabled": True,
"responsive_web_media_download_video_enabled": False,
# "rweb_lists_timeline_redesign_enabled": True,
"responsive_web_twitter_article_tweet_consumption_enabled": False,
# "responsive_web_home_pinned_timelines_enabled": True,
"c9s_tweet_anatomy_moderator_badge_enabled": True,
"rweb_video_timestamps_enabled": True,
}

Просмотреть файл

@ -3,11 +3,12 @@ from datetime import timedelta
from httpx import AsyncClient, HTTPStatusError, Response from httpx import AsyncClient, HTTPStatusError, Response
from .account import Account from .account import Account
from .constants import LOGIN_URL
from .imap import imap_get_email_code, imap_login from .imap import imap_get_email_code, imap_login
from .logger import logger from .logger import logger
from .utils import raise_for_status, utc from .utils import raise_for_status, utc
LOGIN_URL = "https://api.twitter.com/1.1/onboarding/task.json"
async def get_guest_token(client: AsyncClient): async def get_guest_token(client: AsyncClient):
rep = await client.post("https://api.twitter.com/1.1/guest/activate.json") rep = await client.post("https://api.twitter.com/1.1/guest/activate.json")