Use ruff for formatting/linting

This commit is contained in:
Sonny Bakker 2024-09-05 06:58:35 +02:00
parent bb74e875e0
commit 57375591b5
36 changed files with 241 additions and 245 deletions

View file

@ -5,14 +5,8 @@ python-linting:
- pip install uv - pip install uv
- uv sync --extra testing --extra ci - uv sync --extra testing --extra ci
script: script:
- ./.venv/bin/isort --check-only src/ - ./.venv/bin/ruff --check src/
- ./.venv/bin/black --line-length 88 --check src/ - ./.venv/bin/ruff format --check src/
- |
./.venv/bin/autoflake --check \
--recursive \
--remove-all-unused-imports \
--ignore-init-module-imports \
src/
only: only:
refs: refs:
- development - development

View file

@ -29,20 +29,30 @@ dependencies = [
testing = [ testing = [
'factory-boy', 'factory-boy',
'freezegun', 'freezegun',
'black',
'isort',
'autoflake',
'tblib', 'tblib',
"ruff>=0.6.3",
] ]
development = [ development = [
'django-debug-toolbar', 'django-debug-toolbar',
'django-extensions', 'django-extensions',
] ]
ci = ['coverage>=5.3.1'] ci = ['coverage>=5.3.1']
production = ['gunicorn~=20.0', 'sentry-sdk~=1.0'] production = ['gunicorn~=20.0', 'sentry-sdk~=1.0']
[tool.uv] [tool.uv]
environments = ["sys_platform == 'linux'"] environments = ["sys_platform == 'linux'"]
[tool.ruff]
include = ['pyproject.toml', 'src/**/*.py']
line-length = 88
[tool.ruff.lint.isort]
default-section = 'third-party'
known-first-party = ['newsreader']
lines-between-types=1
lines-after-imports=2
[tool.ruff.lint.isort.sections]
django = ['django']

View file

@ -1,5 +1,6 @@
#!/usr/bin/env python #!/usr/bin/env python
"""Django's command-line utility for administrative tasks.""" """Django's command-line utility for administrative tasks."""
import os import os
import sys import sys

View file

@ -57,7 +57,7 @@ class ResendActivationTestCase(TestCase):
def test_existing_account(self): def test_existing_account(self):
user = UserFactory(is_active=True) user = UserFactory(is_active=True)
profile = RegistrationProfileFactory(user=user, activated=True) RegistrationProfileFactory(user=user, activated=True)
response = self.client.post(self.url, {"email": user.email}) response = self.client.post(self.url, {"email": user.email})
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)

View file

@ -25,3 +25,28 @@ from newsreader.accounts.views.registration import (
RegistrationView, RegistrationView,
) )
from newsreader.accounts.views.settings import SettingsView from newsreader.accounts.views.settings import SettingsView
__all__ = [
"LoginView",
"LogoutView",
"FaviconRedirectView",
"IntegrationsView",
"RedditRevokeRedirectView",
"RedditTemplateView",
"RedditTokenRedirectView",
"TwitterAuthRedirectView",
"TwitterRevokeRedirectView",
"TwitterTemplateView",
"PasswordChangeView",
"PasswordResetCompleteView",
"PasswordResetConfirmView",
"PasswordResetDoneView",
"PasswordResetView",
"ActivationCompleteView",
"ActivationResendView",
"ActivationView",
"RegistrationClosedView",
"RegistrationCompleteView",
"RegistrationView",
"SettingsView",
]

View file

@ -1,14 +1,14 @@
from .base import * # isort:skip from .base import * # noqa: F403
from .version import get_current_version from .version import get_current_version
SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl" SECRET_KEY = "mv4&5#+)-=abz3^&1r^nk_ca6y54--p(4n4cg%z*g&rb64j%wl"
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
# Project settings # Project settings
VERSION = get_current_version() VERSION = get_current_version()
@ -23,8 +23,8 @@ try:
from .local import * # noqa from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION}) SENTRY_CONFIG.update({"release": VERSION}) # noqa: F405
sentry_init(**SENTRY_CONFIG) sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError: except ImportError:
pass pass

View file

@ -1,14 +1,14 @@
from .base import * # isort:skip from .base import * # noqa: F403
from .version import get_current_version from .version import get_current_version
ALLOWED_HOSTS = ["django", "127.0.0.1"] ALLOWED_HOSTS = ["django", "127.0.0.1"]
INSTALLED_APPS += ["debug_toolbar", "django_extensions"] INSTALLED_APPS += ["debug_toolbar", "django_extensions"] # noqa: F405
MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] MIDDLEWARE += ["debug_toolbar.middleware.DebugToolbarMiddleware"] # noqa: F405
LOGGING["loggers"].update( LOGGING["loggers"].update( # noqa: F405
{ {
"celery.task": {"handlers": ["console", "celery"], "level": "DEBUG"}, "celery.task": {"handlers": ["console", "celery"], "level": "DEBUG"},
} }
@ -33,8 +33,8 @@ try:
from .local import * # noqa from .local import * # noqa
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) # noqa: F405
sentry_init(**SENTRY_CONFIG) sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError: except ImportError:
pass pass

View file

@ -1,14 +1,14 @@
from .base import * # isort:skip from .base import * # noqa: F403
from .version import get_current_version from .version import get_current_version
DEBUG = True DEBUG = True
del LOGGING["handlers"]["file"] del LOGGING["handlers"]["file"] # noqa: F405
del LOGGING["handlers"]["celery"] del LOGGING["handlers"]["celery"] # noqa: F405
LOGGING["loggers"].update( LOGGING["loggers"].update( # noqa: F405
{ {
"celery.task": {"handlers": ["console"], "level": "DEBUG"}, "celery.task": {"handlers": ["console"], "level": "DEBUG"},
"newsreader": {"handlers": ["console"], "level": "INFO"}, "newsreader": {"handlers": ["console"], "level": "INFO"},
@ -39,8 +39,8 @@ try:
# Optionally use sentry integration # Optionally use sentry integration
from sentry_sdk import init as sentry_init from sentry_sdk import init as sentry_init
SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) SENTRY_CONFIG.update({"release": VERSION, "environment": ENVIRONMENT}) # noqa: F405
sentry_init(**SENTRY_CONFIG) sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError: except ImportError:
pass pass

View file

@ -3,7 +3,7 @@ import os
from .version import get_current_version from .version import get_current_version
from .base import * # isort:skip from .base import * # noqa: F403
DEBUG = False DEBUG = False
@ -20,7 +20,7 @@ ADMINS = [
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")], "DIRS": [os.path.join(DJANGO_PROJECT_DIR, "templates")], # noqa: F405
"APP_DIRS": True, "APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
"context_processors": [ "context_processors": [
@ -68,10 +68,10 @@ REGISTRATION_OPEN = False
try: try:
from sentry_sdk import init as sentry_init from sentry_sdk import init as sentry_init
SENTRY_CONFIG.update( SENTRY_CONFIG.update( # noqa: F405
{"release": VERSION, "environment": ENVIRONMENT, "debug": False} {"release": VERSION, "environment": ENVIRONMENT, "debug": False}
) )
sentry_init(**SENTRY_CONFIG) sentry_init(**SENTRY_CONFIG) # noqa: F405
except ImportError: except ImportError:
pass pass

View file

@ -15,3 +15,20 @@ from newsreader.news.collection.exceptions.stream import (
StreamTimeOutException, StreamTimeOutException,
StreamTooManyException, StreamTooManyException,
) )
__all__ = [
"BuilderDuplicateException",
"BuilderException",
"BuilderMissingDataException",
"BuilderParseException",
"BuilderSkippedException",
"StreamConnectionException",
"StreamDeniedException",
"StreamException",
"StreamForbiddenException",
"StreamNotFoundException",
"StreamParseException",
"StreamTimeOutException",
"StreamTooManyException",
]

View file

@ -81,7 +81,7 @@ class FaviconBuilder(Builder):
icons = set() icons = set()
for link in links: for link in links:
if not "href" in link.attrs: if "href" not in link.attrs:
continue continue
if "favicon" in link["href"]: if "favicon" in link["href"]:

View file

@ -62,7 +62,7 @@ class FeedBuilder(PostBuilder):
data = {"rule_id": self.stream.rule.pk} data = {"rule_id": self.stream.rule.pk}
for field, model_field in field_mapping.items(): for field, model_field in field_mapping.items():
if not field in entry: if field not in entry:
continue continue
value = truncate_text(Post, model_field, entry[field]) value = truncate_text(Post, model_field, entry[field])
@ -77,7 +77,7 @@ class FeedBuilder(PostBuilder):
content_details = self.get_content_details(entry) content_details = self.get_content_details(entry)
# use content details key if it contains more information # use content details key if it contains more information
if not "body" in data or len(data["body"]) < len(content_details): if "body" not in data or len(data["body"]) < len(content_details):
data["body"] = content_details data["body"] = content_details
return Post(**data) return Post(**data)

View file

@ -2,3 +2,11 @@ from newsreader.news.collection.forms.feed import FeedForm, OPMLImportForm
from newsreader.news.collection.forms.reddit import SubRedditForm from newsreader.news.collection.forms.reddit import SubRedditForm
from newsreader.news.collection.forms.rules import CollectionRuleBulkForm from newsreader.news.collection.forms.rules import CollectionRuleBulkForm
from newsreader.news.collection.forms.twitter import TwitterTimelineForm from newsreader.news.collection.forms.twitter import TwitterTimelineForm
__all__ = [
"FeedForm",
"OPMLImportForm",
"SubRedditForm",
"CollectionRuleBulkForm",
"TwitterTimelineForm",
]

View file

@ -120,7 +120,7 @@ class RedditBuilder(PostBuilder):
def build(self): def build(self):
results = {} results = {}
if not "data" in self.payload or not "children" in self.payload["data"]: if "data" not in self.payload or "children" not in self.payload["data"]:
return return
entries = self.payload["data"]["children"] entries = self.payload["data"]["children"]
@ -297,9 +297,7 @@ class RedditStream(PostStream):
def __init__(self, rule): def __init__(self, rule):
super().__init__(rule) super().__init__(rule)
self.headers = { self.headers = {"Authorization": f"bearer {self.rule.user.reddit_access_token}"}
f"Authorization": f"bearer {self.rule.user.reddit_access_token}"
}
def read(self): def read(self):
response = fetch(self.rule.url, headers=self.headers) response = fetch(self.rule.url, headers=self.headers)

View file

@ -11,6 +11,7 @@ from celery.utils.log import get_task_logger
from newsreader.accounts.models import User from newsreader.accounts.models import User
from newsreader.celery import app from newsreader.celery import app
from newsreader.news.collection.choices import RuleTypeChoices from newsreader.news.collection.choices import RuleTypeChoices
from newsreader.news.collection.exceptions.stream import StreamException
from newsreader.news.collection.feed import FeedCollector from newsreader.news.collection.feed import FeedCollector
from newsreader.news.collection.utils import post from newsreader.news.collection.utils import post
from newsreader.utils.celery import MemCacheLock from newsreader.utils.celery import MemCacheLock
@ -57,7 +58,7 @@ class RedditTask(app.Task):
with MemCacheLock("reddit-task", self.app.oid) as acquired: with MemCacheLock("reddit-task", self.app.oid) as acquired:
if acquired: if acquired:
logger.info(f"Running reddit task") logger.info("Running reddit task")
scheduler = RedditScheduler() scheduler = RedditScheduler()
subreddits = scheduler.get_scheduled_rules() subreddits = scheduler.get_scheduled_rules()
@ -65,7 +66,7 @@ class RedditTask(app.Task):
collector = RedditCollector() collector = RedditCollector()
collector.collect(rules=subreddits) collector.collect(rules=subreddits)
else: else:
logger.warning(f"Cancelling task due to existing lock") logger.warning("Cancelling task due to existing lock")
raise Reject(reason="Task already running", requeue=False) raise Reject(reason="Task already running", requeue=False)
@ -154,7 +155,7 @@ class TwitterTimelineTask(app.Task):
collector = TwitterCollector() collector = TwitterCollector()
collector.collect(rules=timelines) collector.collect(rules=timelines)
else: else:
logger.warning(f"Cancelling task due to existing lock") logger.warning("Cancelling task due to existing lock")
raise Reject(reason="Task already running", requeue=False) raise Reject(reason="Task already running", requeue=False)
@ -195,7 +196,7 @@ class FaviconTask(app.Task):
rule.favicon = "https://abs.twimg.com/favicons/favicon.ico" rule.favicon = "https://abs.twimg.com/favicons/favicon.ico"
rule.save() rule.save()
else: else:
logger.warning(f"Cancelling task due to existing lock") logger.warning("Cancelling task due to existing lock")
raise Reject(reason="Task already running", requeue=False) raise Reject(reason="Task already running", requeue=False)

View file

@ -64,22 +64,6 @@ class CollectionRuleDetailViewTestCase(TestCase):
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(data["name"], "The guardian") self.assertEquals(data["name"], "The guardian")
def test_category_change(self):
old_category = CategoryFactory(user=self.user)
new_category = CategoryFactory(user=self.user)
rule = FeedFactory(name="BBC", category=old_category, user=self.user)
response = self.client.patch(
reverse("api:news:collection:rules-detail", args=[rule.pk]),
data=json.dumps({"category": absolute_url}),
content_type="application/json",
)
data = response.json()
self.assertEquals(response.status_code, 200)
self.assertEquals(data["category"], new_category.pk)
def test_identifier_cannot_be_changed(self): def test_identifier_cannot_be_changed(self):
rule = FeedFactory(user=self.user) rule = FeedFactory(user=self.user)

View file

@ -4,7 +4,14 @@ from django.test import TestCase
from newsreader.news.collection.favicon import FaviconBuilder from newsreader.news.collection.favicon import FaviconBuilder
from newsreader.news.collection.tests.factories import CollectionRuleFactory from newsreader.news.collection.tests.factories import CollectionRuleFactory
from newsreader.news.collection.tests.favicon.builder.mocks import * from newsreader.news.collection.tests.favicon.builder.mocks import (
simple_mock,
mock_without_url,
mock_without_header,
mock_with_weird_path,
mock_with_other_url,
mock_with_multiple_icons,
)
class FaviconBuilderTestCase(TestCase): class FaviconBuilderTestCase(TestCase):

View file

@ -134,7 +134,6 @@ feed_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"links": [ "links": [

View file

@ -108,8 +108,6 @@ mock_without_url = {
"id": "https://www.bbc.co.uk/news/world-us-canada-48338168", "id": "https://www.bbc.co.uk/news/world-us-canada-48338168",
"published": "Mon, 20 May 2019 16:07:37 GMT", "published": "Mon, 20 May 2019 16:07:37 GMT",
"published_parsed": struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0)), "published_parsed": struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0)),
"published": None,
"published_parsed": None,
"summary": "Foreign Minister Mohammad Javad Zarif says the US " "summary": "Foreign Minister Mohammad Javad Zarif says the US "
"president should try showing Iranians some respect.", "president should try showing Iranians some respect.",
"title": "Trump's 'genocidal taunts' will not end Iran - Zarif", "title": "Trump's 'genocidal taunts' will not end Iran - Zarif",

View file

@ -13,7 +13,23 @@ from newsreader.news.collection.tests.factories import FeedFactory
from newsreader.news.core.models import Post from newsreader.news.core.models import Post
from newsreader.news.core.tests.factories import FeedPostFactory from newsreader.news.core.tests.factories import FeedPostFactory
from .mocks import * from .mocks import (
multiple_mock,
mock_without_identifier,
mock_without_publish_date,
mock_without_url,
mock_without_body,
mock_without_author,
mock_without_entries,
mock_with_update_entries,
mock_with_html,
mock_with_long_author,
mock_with_long_title,
mock_with_long_exotic_title,
mock_with_longer_content_detail,
mock_with_shorter_content_detail,
mock_with_multiple_content_detail,
)
@freeze_time("2019-10-30 12:30:00") @freeze_time("2019-10-30 12:30:00")

View file

@ -52,7 +52,6 @@ simple_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"links": [ "links": [
{ {

View file

@ -132,7 +132,6 @@ multiple_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"links": [ "links": [
{ {
@ -158,7 +157,6 @@ empty_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"links": [ "links": [
{ {
@ -302,7 +300,6 @@ duplicate_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"links": [ "links": [
{ {
@ -449,7 +446,6 @@ multiple_update_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"links": [ "links": [
{ {

View file

@ -142,7 +142,7 @@ class FeedCollectorTestCase(TestCase):
struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0)), pytz.utc struct_time((2019, 5, 20, 16, 7, 37, 0, 140, 0)), pytz.utc
) )
first_post = FeedPostFactory( FeedPostFactory(
url="https://www.bbc.co.uk/news/world-us-canada-48338168", url="https://www.bbc.co.uk/news/world-us-canada-48338168",
title="Trump's 'genocidal taunts' will not end Iran - Zarif", title="Trump's 'genocidal taunts' will not end Iran - Zarif",
body="Foreign Minister Mohammad Javad Zarif says the US " body="Foreign Minister Mohammad Javad Zarif says the US "
@ -155,7 +155,7 @@ class FeedCollectorTestCase(TestCase):
struct_time((2019, 5, 20, 12, 19, 19, 0, 140, 0)), pytz.utc struct_time((2019, 5, 20, 12, 19, 19, 0, 140, 0)), pytz.utc
) )
second_post = FeedPostFactory( FeedPostFactory(
url="https://www.bbc.co.uk/news/technology-48334739", url="https://www.bbc.co.uk/news/technology-48334739",
title="Huawei's Android loss: How it affects you", title="Huawei's Android loss: How it affects you",
body="Google's move to end business ties with Huawei will " body="Google's move to end business ties with Huawei will "
@ -168,7 +168,7 @@ class FeedCollectorTestCase(TestCase):
struct_time((2019, 5, 20, 16, 32, 38, 0, 140, 0)), pytz.utc struct_time((2019, 5, 20, 16, 32, 38, 0, 140, 0)), pytz.utc
) )
third_post = FeedPostFactory( FeedPostFactory(
url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080", url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
title="Birmingham head teacher threatened over LGBT lessons", title="Birmingham head teacher threatened over LGBT lessons",
body="Police are investigating the messages while an MP " body="Police are investigating the messages while an MP "

View file

@ -19,7 +19,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_with_remote_identifiers(self): def test_duplicate_entries_with_remote_identifiers(self):
rule = FeedFactory() rule = FeedFactory()
existing_post = FeedPostFactory.create( FeedPostFactory.create(
remote_identifier="28f79ae4-8f9a-11e9-b143-00163ef6bee7", rule=rule remote_identifier="28f79ae4-8f9a-11e9-b143-00163ef6bee7", rule=rule
) )
@ -54,7 +54,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_with_different_remote_identifiers(self): def test_duplicate_entries_with_different_remote_identifiers(self):
rule = FeedFactory() rule = FeedFactory()
existing_post = FeedPostFactory( FeedPostFactory(
remote_identifier="28f79ae4-8f9a-11e9-b143-00163ef6bee7", remote_identifier="28f79ae4-8f9a-11e9-b143-00163ef6bee7",
url="https://bbc.com", url="https://bbc.com",
title="New post", title="New post",
@ -100,7 +100,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_in_recent_database(self): def test_duplicate_entries_in_recent_database(self):
rule = FeedFactory() rule = FeedFactory()
existing_post = FeedPostFactory( FeedPostFactory(
url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080", url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
title="Birmingham head teacher threatened over LGBT lessons", title="Birmingham head teacher threatened over LGBT lessons",
body="Google's move to end business ties with Huawei will affect current devices", body="Google's move to end business ties with Huawei will affect current devices",
@ -189,7 +189,7 @@ class FeedDuplicateHandlerTestCase(TestCase):
def test_duplicate_entries_outside_time_slot(self): def test_duplicate_entries_outside_time_slot(self):
rule = FeedFactory() rule = FeedFactory()
existing_post = FeedPostFactory( FeedPostFactory(
url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080", url="https://www.bbc.co.uk/news/uk-england-birmingham-48339080",
title="Birmingham head teacher threatened over LGBT lessons", title="Birmingham head teacher threatened over LGBT lessons",
body="Google's move to end business ties with Huawei will affect current devices", body="Google's move to end business ties with Huawei will affect current devices",

View file

@ -17,7 +17,6 @@ simple_feed_mock = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"links": [ "links": [
@ -43,7 +42,6 @@ feed_mock_without_link = {
"link": "https://www.bbc.co.uk/news/", "link": "https://www.bbc.co.uk/news/",
"title": "BBC News - Home", "title": "BBC News - Home",
"language": "en-gb", "language": "en-gb",
"link": "https://www.bbc.co.uk/news/",
}, },
"title": "BBC News - Home", "title": "BBC News - Home",
}, },

View file

@ -677,16 +677,6 @@ empty_mock = {
}, },
} }
unknown_mock = {
"kind": "Comment",
"data": {
"modhash": "rjewztai5w0ab64547311ae1fb1f9cf81cd18949bfb629cb7f",
"dist": 27,
"after": "t3_hmytic",
"before": None,
},
}
unsanitized_mock = { unsanitized_mock = {
"kind": "Listing", "kind": "Listing",
"data": { "data": {

View file

@ -7,7 +7,26 @@ import pytz
from newsreader.news.collection.reddit import RedditBuilder from newsreader.news.collection.reddit import RedditBuilder
from newsreader.news.collection.tests.factories import SubredditFactory from newsreader.news.collection.tests.factories import SubredditFactory
from newsreader.news.collection.tests.reddit.builder.mocks import * from newsreader.news.collection.tests.reddit.builder.mocks import (
simple_mock,
empty_mock,
unknown_mock,
unsanitized_mock,
author_mock,
title_mock,
duplicate_mock,
image_mock,
external_image_mock,
video_mock,
external_video_mock,
external_gifv_mock,
nsfw_mock,
spoiler_mock,
seen_mock,
upvote_mock,
comment_mock,
downvote_mock,
)
from newsreader.news.core.models import Post from newsreader.news.core.models import Post
from newsreader.news.core.tests.factories import RedditPostFactory from newsreader.news.core.tests.factories import RedditPostFactory
@ -164,9 +183,7 @@ class RedditBuilderTestCase(TestCase):
subreddit = SubredditFactory() subreddit = SubredditFactory()
mock_stream = Mock(rule=subreddit) mock_stream = Mock(rule=subreddit)
duplicate_post = RedditPostFactory( RedditPostFactory(remote_identifier="hm0qct", rule=subreddit, title="foo")
remote_identifier="hm0qct", rule=subreddit, title="foo"
)
with builder(simple_mock, mock_stream) as builder: with builder(simple_mock, mock_stream) as builder:
builder.build() builder.build()

View file

@ -150,7 +150,6 @@ class RedditClientTestCase(TestCase):
def test_client_catches_long_exception_text(self): def test_client_catches_long_exception_text(self):
subreddit = SubredditFactory() subreddit = SubredditFactory()
mock_stream = Mock(rule=subreddit)
self.mocked_read.side_effect = StreamParseException(message=words(1000)) self.mocked_read.side_effect = StreamParseException(message=words(1000))

View file

@ -153,7 +153,6 @@ class TwitterClientTestCase(TestCase):
def test_client_catches_long_exception_text(self): def test_client_catches_long_exception_text(self):
timeline = TwitterTimelineFactory() timeline = TwitterTimelineFactory()
mock_stream = Mock(rule=timeline)
self.mocked_read.side_effect = StreamParseException(message=words(1000)) self.mocked_read.side_effect = StreamParseException(message=words(1000))

View file

@ -17,3 +17,18 @@ from newsreader.news.collection.views.twitter import (
TwitterTimelineCreateView, TwitterTimelineCreateView,
TwitterTimelineUpdateView, TwitterTimelineUpdateView,
) )
__all__ = [
"FeedCreateView",
"FeedUpdateView",
"OPMLImportView",
"SubRedditCreateView",
"SubRedditUpdateView",
"CollectionRuleBulkDeleteView",
"CollectionRuleBulkDisableView",
"CollectionRuleBulkEnableView",
"CollectionRuleListView",
"TwitterTimelineCreateView",
"TwitterTimelineUpdateView",
]

View file

@ -138,10 +138,10 @@ class CategoryReadTestCase(TestCase):
def test_category_read(self): def test_category_read(self):
category = CategoryFactory(user=self.user) category = CategoryFactory(user=self.user)
rules = [ rules = FeedFactory.create_batch(size=5, category=category)
for rule in rules:
FeedPostFactory.create_batch(size=5, read=False, rule=rule) FeedPostFactory.create_batch(size=5, read=False, rule=rule)
for rule in FeedFactory.create_batch(size=5, category=category)
]
response = self.client.post( response = self.client.post(
reverse("api:news:core:categories-read", args=[category.pk]) reverse("api:news:core:categories-read", args=[category.pk])
@ -164,12 +164,10 @@ class CategoryReadTestCase(TestCase):
self.client.logout() self.client.logout()
category = CategoryFactory(user=self.user) category = CategoryFactory(user=self.user)
rules = [ rules = FeedFactory.create_batch(size=5, category=category, user=self.user)
for rule in rules:
FeedPostFactory.create_batch(size=5, read=False, rule=rule) FeedPostFactory.create_batch(size=5, read=False, rule=rule)
for rule in FeedFactory.create_batch(
size=5, category=category, user=self.user
)
]
response = self.client.post( response = self.client.post(
reverse("api:news:core:categories-read", args=[category.pk]) reverse("api:news:core:categories-read", args=[category.pk])
@ -180,13 +178,10 @@ class CategoryReadTestCase(TestCase):
def test_unauthorized_user(self): def test_unauthorized_user(self):
other_user = UserFactory() other_user = UserFactory()
category = CategoryFactory(user=other_user) category = CategoryFactory(user=other_user)
rules = FeedFactory.create_batch(size=5, category=category, user=other_user)
rules = [ for rule in rules:
FeedPostFactory.create_batch(size=5, read=False, rule=rule) FeedPostFactory.create_batch(size=5, read=False, rule=rule)
for rule in FeedFactory.create_batch(
size=5, category=category, user=other_user
)
]
response = self.client.post( response = self.client.post(
reverse("api:news:core:categories-read", args=[category.pk]) reverse("api:news:core:categories-read", args=[category.pk])

View file

@ -119,7 +119,7 @@ class NestedCategoryListViewTestCase(TestCase):
def test_simple(self): def test_simple(self):
category = CategoryFactory.create(user=self.user) category = CategoryFactory.create(user=self.user)
rules = FeedFactory.create_batch(size=5, category=category) FeedFactory.create_batch(size=5, category=category)
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
@ -213,7 +213,7 @@ class NestedCategoryListViewTestCase(TestCase):
self.client.logout() self.client.logout()
category = CategoryFactory.create(user=self.user) category = CategoryFactory.create(user=self.user)
rules = FeedFactory.create_batch(size=5, category=category) FeedFactory.create_batch(size=5, category=category)
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
@ -225,7 +225,7 @@ class NestedCategoryListViewTestCase(TestCase):
other_user = UserFactory.create() other_user = UserFactory.create()
category = CategoryFactory.create(user=other_user) category = CategoryFactory.create(user=other_user)
rules = FeedFactory.create_batch(size=5, category=category) FeedFactory.create_batch(size=5, category=category)
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-rules", kwargs={"pk": category.pk})
@ -284,12 +284,10 @@ class NestedCategoryPostView(TestCase):
def test_simple(self): def test_simple(self):
category = CategoryFactory.create(user=self.user) category = CategoryFactory.create(user=self.user)
rules = { rules = FeedFactory.create_batch(size=5, category=category, user=self.user)
rule.pk: FeedPostFactory.create_batch(size=5, rule=rule)
for rule in FeedFactory.create_batch( for rule in rules:
size=5, category=category, user=self.user FeedPostFactory.create_batch(size=5, rule=rule)
)
}
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
@ -320,7 +318,7 @@ class NestedCategoryPostView(TestCase):
def test_no_posts(self): def test_no_posts(self):
category = CategoryFactory.create(user=self.user) category = CategoryFactory.create(user=self.user)
rules = FeedFactory.create_batch(size=5, user=self.user, category=category) FeedFactory.create_batch(size=5, user=self.user, category=category)
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
@ -439,31 +437,29 @@ class NestedCategoryPostView(TestCase):
), ),
] ]
guardian_posts = [ FeedPostFactory.create(
FeedPostFactory.create( title="Second Guardian post",
title="Second Guardian post", rule=guardian_rule,
rule=guardian_rule, publication_date=datetime(2019, 5, 21, 14, tzinfo=pytz.utc),
publication_date=datetime(2019, 5, 21, 14, tzinfo=pytz.utc), )
),
FeedPostFactory.create(
title="First Guardian post",
rule=guardian_rule,
publication_date=datetime(2019, 5, 20, 11, tzinfo=pytz.utc),
),
]
bbc_posts = [ FeedPostFactory.create(
FeedPostFactory.create( title="First Guardian post",
title="Second BBC post", rule=guardian_rule,
rule=bbc_rule, publication_date=datetime(2019, 5, 20, 11, tzinfo=pytz.utc),
publication_date=datetime(2019, 5, 21, 16, tzinfo=pytz.utc), )
),
FeedPostFactory.create( FeedPostFactory.create(
title="First BBC post", title="Second BBC post",
rule=bbc_rule, rule=bbc_rule,
publication_date=datetime(2019, 5, 20, 13, tzinfo=pytz.utc), publication_date=datetime(2019, 5, 21, 16, tzinfo=pytz.utc),
), )
]
FeedPostFactory.create(
title="First BBC post",
rule=bbc_rule,
publication_date=datetime(2019, 5, 20, 13, tzinfo=pytz.utc),
)
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})
@ -484,22 +480,18 @@ class NestedCategoryPostView(TestCase):
def test_only_posts_from_category_are_returned(self): def test_only_posts_from_category_are_returned(self):
category = CategoryFactory.create(user=self.user) category = CategoryFactory.create(user=self.user)
other_category = CategoryFactory.create(user=self.user) CategoryFactory.create(user=self.user)
guardian_rule = FeedFactory.create( guardian_rule = FeedFactory.create(
name="BBC", category=category, user=self.user name="BBC", category=category, user=self.user
) )
other_rule = FeedFactory.create(name="The Guardian", user=self.user) other_rule = FeedFactory.create(name="The Guardian", user=self.user)
guardian_posts = [ FeedPostFactory.create(rule=guardian_rule)
FeedPostFactory.create(rule=guardian_rule), FeedPostFactory.create(rule=guardian_rule)
FeedPostFactory.create(rule=guardian_rule),
]
other_posts = [ FeedPostFactory.create(rule=other_rule)
FeedPostFactory.create(rule=other_rule), FeedPostFactory.create(rule=other_rule)
FeedPostFactory.create(rule=other_rule),
]
response = self.client.get( response = self.client.get(
reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk}) reverse("api:news:core:categories-nested-posts", kwargs={"pk": category.pk})

View file

@ -55,9 +55,7 @@ class CategoryCreateViewTestCase(CategoryViewTestCase, TestCase):
size=4, user=other_user, category=None size=4, user=other_user, category=None
) )
user_rules = CollectionRuleFactory.create_batch( CollectionRuleFactory.create_batch(size=3, user=self.user, category=None)
size=3, user=self.user, category=None
)
data = { data = {
"name": "new-category", "name": "new-category",

View file

@ -21,7 +21,7 @@ def parse_opml(file, user, skip_existing=False):
validate = URLValidator(schemes=["http", "https"]) validate = URLValidator(schemes=["http", "https"])
for element in root.iter(tag="outline"): for element in root.iter(tag="outline"):
if not "xmlUrl" in element.keys(): if "xmlUrl" not in element.keys():
continue continue
feed_url = element.get("xmlUrl") feed_url = element.get("xmlUrl")

114
uv.lock generated
View file

@ -28,18 +28,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 }, { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828 },
] ]
[[package]]
name = "autoflake"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyflakes", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/2a/cb/486f912d6171bc5748c311a2984a301f4e2d054833a1da78485866c71522/autoflake-2.3.1.tar.gz", hash = "sha256:c98b75dc5b0a86459c4f01a1d32ac7eb4338ec4317a4469515ff1e687ecd909e", size = 27642 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a2/ee/3fd29bf416eb4f1c5579cf12bf393ae954099258abd7bde03c4f9716ef6b/autoflake-2.3.1-py3-none-any.whl", hash = "sha256:3ae7495db9084b7b32818b4140e6dc4fc280b712fb414f5b8fe57b0a8e85a840", size = 32483 },
]
[[package]] [[package]]
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.12.3" version = "4.12.3"
@ -61,30 +49,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/50/8d/6e9fdeeab04d803abc5a715175f87e88893934d5590595eacff23ca12b07/billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d", size = 86720 }, { url = "https://files.pythonhosted.org/packages/50/8d/6e9fdeeab04d803abc5a715175f87e88893934d5590595eacff23ca12b07/billiard-4.2.0-py3-none-any.whl", hash = "sha256:07aa978b308f334ff8282bd4a746e681b3513db5c9a514cbdd810cbbdc19714d", size = 86720 },
] ]
[[package]]
name = "black"
version = "24.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click", marker = "sys_platform == 'linux'" },
{ name = "mypy-extensions", marker = "sys_platform == 'linux'" },
{ name = "packaging", marker = "sys_platform == 'linux'" },
{ name = "pathspec", marker = "sys_platform == 'linux'" },
{ name = "platformdirs", marker = "sys_platform == 'linux'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/b0/46fb0d4e00372f4a86a6f8efa3cb193c9f64863615e39010b1477e010578/black-24.8.0.tar.gz", hash = "sha256:2500945420b6784c38b9ee885af039f5e7471ef284ab03fa35ecdde4688cd83f", size = 644810 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/08/a6/0a3aa89de9c283556146dc6dbda20cd63a9c94160a6fbdebaf0918e4a3e1/black-24.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fb6e2c0b86bbd43dee042e48059c9ad7830abd5c94b0bc518c0eeec57c3eddc1", size = 1615080 },
{ url = "https://files.pythonhosted.org/packages/db/94/b803d810e14588bb297e565821a947c108390a079e21dbdcb9ab6956cd7a/black-24.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:837fd281f1908d0076844bc2b801ad2d369c78c45cf800cad7b61686051041af", size = 1438143 },
{ url = "https://files.pythonhosted.org/packages/a5/b5/f485e1bbe31f768e2e5210f52ea3f432256201289fd1a3c0afda693776b0/black-24.8.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62e8730977f0b77998029da7971fa896ceefa2c4c4933fcd593fa599ecbf97a4", size = 1738774 },
{ url = "https://files.pythonhosted.org/packages/a8/69/a000fc3736f89d1bdc7f4a879f8aaf516fb03613bb51a0154070383d95d9/black-24.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:72901b4913cbac8972ad911dc4098d5753704d1f3c56e44ae8dce99eecb0e3af", size = 1427503 },
{ url = "https://files.pythonhosted.org/packages/a2/a8/05fb14195cfef32b7c8d4585a44b7499c2a4b205e1662c427b941ed87054/black-24.8.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c046c1d1eeb7aea9335da62472481d3bbf3fd986e093cffd35f4385c94ae368", size = 1646132 },
{ url = "https://files.pythonhosted.org/packages/41/77/8d9ce42673e5cb9988f6df73c1c5c1d4e9e788053cccd7f5fb14ef100982/black-24.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:649f6d84ccbae73ab767e206772cc2d7a393a001070a4c814a546afd0d423aed", size = 1448665 },
{ url = "https://files.pythonhosted.org/packages/cc/94/eff1ddad2ce1d3cc26c162b3693043c6b6b575f538f602f26fe846dfdc75/black-24.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b59b250fdba5f9a9cd9d0ece6e6d993d91ce877d121d161e4698af3eb9c1018", size = 1762458 },
{ url = "https://files.pythonhosted.org/packages/28/ea/18b8d86a9ca19a6942e4e16759b2fa5fc02bbc0eb33c1b866fcd387640ab/black-24.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:6e55d30d44bed36593c3163b9bc63bf58b3b30e4611e4d88a0c3c239930ed5b2", size = 1436109 },
{ url = "https://files.pythonhosted.org/packages/27/1e/83fa8a787180e1632c3d831f7e58994d7aaf23a0961320d21e84f922f919/black-24.8.0-py3-none-any.whl", hash = "sha256:972085c618ee94f402da1af548a4f218c754ea7e5dc70acb168bfaca4c2542ed", size = 206504 },
]
[[package]] [[package]]
name = "bleach" name = "bleach"
version = "6.1.0" version = "6.1.0"
@ -497,15 +461,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 }, { url = "https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2", size = 9454 },
] ]
[[package]]
name = "isort"
version = "5.13.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/87/f9/c1eb8635a24e87ade2efce21e3ce8cd6b8630bb685ddc9cdaca1349b2eb5/isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109", size = 175303 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/b3/8def84f539e7d2289a02f0524b944b15d7c75dab7628bedf1c4f0992029c/isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6", size = 92310 },
]
[[package]] [[package]]
name = "kombu" name = "kombu"
version = "5.4.0" version = "5.4.0"
@ -578,15 +533,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 }, { url = "https://files.pythonhosted.org/packages/7d/db/214290d58ad68c587bd5d6af3d34e56830438733d0d0856c0275fde43652/lxml-5.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:406246b96d552e0503e17a1006fd27edac678b3fcc9f1be71a2f94b4ff61528d", size = 3814417 },
] ]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
]
[[package]] [[package]]
name = "newsreader" name = "newsreader"
version = "0.4.4" version = "0.4.4"
@ -625,19 +571,15 @@ production = [
{ name = "sentry-sdk", marker = "sys_platform == 'linux'" }, { name = "sentry-sdk", marker = "sys_platform == 'linux'" },
] ]
testing = [ testing = [
{ name = "autoflake", marker = "sys_platform == 'linux'" },
{ name = "black", marker = "sys_platform == 'linux'" },
{ name = "factory-boy", marker = "sys_platform == 'linux'" }, { name = "factory-boy", marker = "sys_platform == 'linux'" },
{ name = "freezegun", marker = "sys_platform == 'linux'" }, { name = "freezegun", marker = "sys_platform == 'linux'" },
{ name = "isort", marker = "sys_platform == 'linux'" }, { name = "ruff", marker = "sys_platform == 'linux'" },
{ name = "tblib", marker = "sys_platform == 'linux'" }, { name = "tblib", marker = "sys_platform == 'linux'" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "autoflake", marker = "extra == 'testing'" },
{ name = "beautifulsoup4" }, { name = "beautifulsoup4" },
{ name = "black", marker = "extra == 'testing'" },
{ name = "bleach" }, { name = "bleach" },
{ name = "celery", specifier = "~=5.0" }, { name = "celery", specifier = "~=5.0" },
{ name = "coverage", marker = "extra == 'ci'", specifier = ">=5.3.1" }, { name = "coverage", marker = "extra == 'ci'", specifier = ">=5.3.1" },
@ -654,13 +596,13 @@ requires-dist = [
{ name = "freezegun", marker = "extra == 'testing'" }, { name = "freezegun", marker = "extra == 'testing'" },
{ name = "ftfy", specifier = "~=5.8" }, { name = "ftfy", specifier = "~=5.8" },
{ name = "gunicorn", marker = "extra == 'production'", specifier = "~=20.0" }, { name = "gunicorn", marker = "extra == 'production'", specifier = "~=20.0" },
{ name = "isort", marker = "extra == 'testing'" },
{ name = "lxml" }, { name = "lxml" },
{ name = "psycopg2" }, { name = "psycopg2" },
{ name = "python-dotenv", specifier = "~=0.12" }, { name = "python-dotenv", specifier = "~=0.12" },
{ name = "python-memcached", specifier = "<=1.59" }, { name = "python-memcached", specifier = "<=1.59" },
{ name = "requests" }, { name = "requests" },
{ name = "requests-oauthlib" }, { name = "requests-oauthlib" },
{ name = "ruff", marker = "extra == 'testing'", specifier = ">=0.6.3" },
{ name = "sentry-sdk", marker = "extra == 'production'", specifier = "~=1.0" }, { name = "sentry-sdk", marker = "extra == 'production'", specifier = "~=1.0" },
{ name = "setuptools", specifier = ">=74.0.0" }, { name = "setuptools", specifier = ">=74.0.0" },
{ name = "tblib", marker = "extra == 'testing'" }, { name = "tblib", marker = "extra == 'testing'" },
@ -684,24 +626,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 },
] ]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
]
[[package]]
name = "platformdirs"
version = "4.2.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f5/52/0763d1d976d5c262df53ddda8d8d4719eedf9594d046f117c25a27261a19/platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3", size = 20916 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/13/2aa1f0e1364feb2c9ef45302f387ac0bd81484e9c9a4c5688a322fbdfd08/platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee", size = 18146 },
]
[[package]] [[package]]
name = "prompt-toolkit" name = "prompt-toolkit"
version = "3.0.47" version = "3.0.47"
@ -726,15 +650,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/58/4b/c4a26e191882b60150bfcb639e416524ae7f8249ab7ee854fb5247f16c40/psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693", size = 1163789 }, { url = "https://files.pythonhosted.org/packages/58/4b/c4a26e191882b60150bfcb639e416524ae7f8249ab7ee854fb5247f16c40/psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693", size = 1163789 },
] ]
[[package]]
name = "pyflakes"
version = "3.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/f9/669d8c9c86613c9d568757c7f5824bd3197d7b1c6c27553bc5618a27cce2/pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f", size = 63788 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725 },
]
[[package]] [[package]]
name = "python-crontab" name = "python-crontab"
version = "3.2.0" version = "3.2.0"
@ -852,6 +767,31 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 }, { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 },
] ]
[[package]]
name = "ruff"
version = "0.6.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5d/f9/0b32e5d1c6f957df49398cd882a011e9488fcbca0d6acfeeea50ccd37a4d/ruff-0.6.3.tar.gz", hash = "sha256:183b99e9edd1ef63be34a3b51fee0a9f4ab95add123dbf89a71f7b1f0c991983", size = 2463514 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/72/68/1da6a1e39a03a229ea57c511691d6225072759cc7764206c3f0989521194/ruff-0.6.3-py3-none-linux_armv6l.whl", hash = "sha256:97f58fda4e309382ad30ede7f30e2791d70dd29ea17f41970119f55bdb7a45c3", size = 9696928 },
{ url = "https://files.pythonhosted.org/packages/6e/59/3b8b1d3a4271c6eb6ceecd3cef19a6d881639a0f18ad651563d6f619aaae/ruff-0.6.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b061e49b5cf3a297b4d1c27ac5587954ccb4ff601160d3d6b2f70b1622194dc", size = 9448462 },
{ url = "https://files.pythonhosted.org/packages/35/4f/b942ecb8bbebe53aa9b33e9b96df88acd50b70adaaed3070f1d92131a1cb/ruff-0.6.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:34e2824a13bb8c668c71c1760a6ac7d795ccbd8d38ff4a0d8471fdb15de910b1", size = 9176190 },
{ url = "https://files.pythonhosted.org/packages/a0/20/b0bcb29d4ee437f3567b73b6905c034e2e94d29b9b826c66daecc1cf6388/ruff-0.6.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bddfbb8d63c460f4b4128b6a506e7052bad4d6f3ff607ebbb41b0aa19c2770d1", size = 10108892 },
{ url = "https://files.pythonhosted.org/packages/9c/e3/211bc759f424e8823a9937e0f678695ca02113c621dfde1fa756f9f26f6d/ruff-0.6.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ced3eeb44df75353e08ab3b6a9e113b5f3f996bea48d4f7c027bc528ba87b672", size = 9476471 },
{ url = "https://files.pythonhosted.org/packages/b2/a3/2ec35a2d7a554364864206f0e46812b92a074ad8a014b923d821ead532aa/ruff-0.6.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47021dff5445d549be954eb275156dfd7c37222acc1e8014311badcb9b4ec8c1", size = 10294802 },
{ url = "https://files.pythonhosted.org/packages/03/8b/56ef687b3489c88886dea48c78fb4969b6b65f18007d0ac450070edd1f58/ruff-0.6.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d7bd20dc07cebd68cc8bc7b3f5ada6d637f42d947c85264f94b0d1cd9d87384", size = 11022372 },
{ url = "https://files.pythonhosted.org/packages/a5/21/327d147feb442adb88975e81e2263102789eba9ad2afa102c661912a482f/ruff-0.6.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:500f166d03fc6d0e61c8e40a3ff853fa8a43d938f5d14c183c612df1b0d6c58a", size = 10596596 },
{ url = "https://files.pythonhosted.org/packages/6c/86/ff386de63729da3e08c8099c57f577a00ec9f3eea711b23ac07cf3588dc5/ruff-0.6.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42844ff678f9b976366b262fa2d1d1a3fe76f6e145bd92c84e27d172e3c34500", size = 11572830 },
{ url = "https://files.pythonhosted.org/packages/38/5d/b33284c108e3f315ddd09b70296fd76bd28ecf8965a520bc93f3bbd8ac40/ruff-0.6.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70452a10eb2d66549de8e75f89ae82462159855e983ddff91bc0bce6511d0470", size = 10262577 },
{ url = "https://files.pythonhosted.org/packages/29/99/9cdfad0d7f460e66567236eddc691473791afd9aff93a0dfcdef0462a6c7/ruff-0.6.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:65a533235ed55f767d1fc62193a21cbf9e3329cf26d427b800fdeacfb77d296f", size = 10098751 },
{ url = "https://files.pythonhosted.org/packages/a8/9f/f801a1619f5549e552f1f722f1db57eb39e7e1d83d482133142781d450de/ruff-0.6.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d2e2c23cef30dc3cbe9cc5d04f2899e7f5e478c40d2e0a633513ad081f7361b5", size = 9563859 },
{ url = "https://files.pythonhosted.org/packages/0b/4d/fb2424faf04ffdb960ae2b3a1d991c5183dd981003de727d2d5cc38abc98/ruff-0.6.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:d8a136aa7d228975a6aee3dd8bea9b28e2b43e9444aa678fb62aeb1956ff2351", size = 9914291 },
{ url = "https://files.pythonhosted.org/packages/2e/dd/94fddf002a8f6152e8ebfbb51d3f93febc415c1fe694345623c31ce8b33b/ruff-0.6.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f92fe93bc72e262b7b3f2bba9879897e2d58a989b4714ba6a5a7273e842ad2f8", size = 10331549 },
{ url = "https://files.pythonhosted.org/packages/b4/73/ca9c2f9237a430ca423b6dca83b77e9a428afeb7aec80596e86c369123fe/ruff-0.6.3-py3-none-win32.whl", hash = "sha256:7a62d3b5b0d7f9143d94893f8ba43aa5a5c51a0ffc4a401aa97a81ed76930521", size = 7962163 },
{ url = "https://files.pythonhosted.org/packages/55/ce/061c605b1dfb52748d59bc0c7a8507546c178801156415773d18febfd71d/ruff-0.6.3-py3-none-win_amd64.whl", hash = "sha256:746af39356fee2b89aada06c7376e1aa274a23493d7016059c3a72e3b296befb", size = 8800901 },
{ url = "https://files.pythonhosted.org/packages/63/28/ae4ffe7d3b6134ca6d31ebef07447ef70097c4a9e8fbbc519b374c5c1559/ruff-0.6.3-py3-none-win_arm64.whl", hash = "sha256:14a9528a8b70ccc7a847637c29e56fd1f9183a9db743bbc5b8e0c4ad60592a82", size = 8229171 },
]
[[package]] [[package]]
name = "sentry-sdk" name = "sentry-sdk"
version = "1.45.1" version = "1.45.1"