diff --git a/tildes/requirements-dev.in b/tildes/requirements-dev.in index fee5842..a369b4c 100644 --- a/tildes/requirements-dev.in +++ b/tildes/requirements-dev.in @@ -3,7 +3,7 @@ black freezegun html5validator mypy -prospector +prospector @ git+https://github.com/Deimos/prospector.git#egg=prospector pyramid-debugtoolbar pytest pytest-mock diff --git a/tildes/requirements-dev.txt b/tildes/requirements-dev.txt index 8e37d9a..81beb78 100644 --- a/tildes/requirements-dev.txt +++ b/tildes/requirements-dev.txt @@ -2,7 +2,7 @@ ago==0.0.93 alembic==1.4.3 appdirs==1.4.4 argon2-cffi==20.1.0 -astroid==2.4.1 +astroid==2.6.2 attrs==20.2.0 backcall==0.2.0 beautifulsoup4==4.9.3 @@ -17,7 +17,7 @@ cornice==5.0.3 decorator==4.4.2 dodgy==0.2.1 flake8-polyfill==1.0.2 -flake8==3.8.4 +flake8==3.9.2 freezegun==1.0.0 gunicorn==20.0.4 html5lib==1.1 @@ -28,10 +28,10 @@ iniconfig==1.1.1 invoke==1.4.1 ipython-genutils==0.2.0 ipython==7.19.0 -isort==4.3.21 +isort==5.9.2 jedi==0.17.2 jinja2==2.11.2 -lazy-object-proxy==1.4.3 +lazy-object-proxy==1.6.0 lupa==1.9 mako==1.1.3 markupsafe==1.1.1 @@ -44,7 +44,7 @@ parso==0.7.1 pastedeploy==2.1.1 pathspec==0.8.0 pep517==0.10.0 -pep8-naming==0.10.0 +pep8-naming==0.12.0 pexpect==4.8.0 pickleshare==0.7.5 pillow==8.0.1 @@ -54,22 +54,19 @@ plaster==1.0 pluggy==0.13.1 prometheus-client==0.8.0 prompt-toolkit==3.0.8 -prospector==1.3.1 +git+https://github.com/Deimos/prospector.git#egg=prospector psycopg2==2.8.6 ptyprocess==0.6.0 publicsuffix2==2.20160818 py==1.9.0 -pycodestyle==2.6.0 +pycodestyle==2.7.0 pycparser==2.20 -pydocstyle==5.1.1 -pyflakes==2.2.0 +pydocstyle==6.1.1 +pyflakes==2.3.1 pygit2==1.6.1 pygments==2.7.2 -pylint-celery==0.3 -pylint-django==2.1.0 -pylint-flask==0.6 pylint-plugin-utils==0.6 -pylint==2.5.3 +pylint==2.9.3 pyotp==2.4.1 pyparsing==2.4.7 pyramid-debugtoolbar==4.8 @@ -84,7 +81,7 @@ pytest-mock==3.3.1 pytest==6.1.2 python-dateutil==2.8.1 python-editor==1.0.4 -pyyaml==5.3.1 +pyyaml==5.4.1 qrcode==6.1 redis==3.5.3 regex==2020.10.28 @@ -94,7 +91,7 @@ requirements-detector==0.7 sentry-sdk==0.19.1 setoptconf==0.2.0 six==1.15.0 -snowballstemmer==2.0.0 +snowballstemmer==2.1.0 soupsieve==2.0.1 sqlalchemy-utils==0.36.8 sqlalchemy==1.3.20 diff --git a/tildes/tildes/__init__.py b/tildes/tildes/__init__.py index 1997d97..c4d62cc 100644 --- a/tildes/tildes/__init__.py +++ b/tildes/tildes/__init__.py @@ -41,6 +41,7 @@ def main(global_config: Dict[str, str], **settings: str) -> PrefixMiddleware: config.add_static_view("images", "/images") if settings.get("sentry_dsn"): + # pylint: disable=abstract-class-instantiated sentry_sdk.init( dsn=settings["sentry_dsn"], integrations=[PyramidIntegration()], diff --git a/tildes/tildes/lib/database.py b/tildes/tildes/lib/database.py index 6faa98d..f2b81d4 100644 --- a/tildes/tildes/lib/database.py +++ b/tildes/tildes/lib/database.py @@ -48,8 +48,8 @@ def obtain_transaction_lock( if lock_space: try: lock_space_value = LockSpaces[lock_space.upper()].value - except KeyError: - raise ValueError("Invalid lock space: %s" % lock_space) + except KeyError as exc: + raise ValueError("Invalid lock space: %s" % lock_space) from exc session.query(func.pg_advisory_xact_lock(lock_space_value, lock_value)).one() else: diff --git a/tildes/tildes/lib/datetime.py b/tildes/tildes/lib/datetime.py index 696d76a..ae20688 100644 --- a/tildes/tildes/lib/datetime.py +++ b/tildes/tildes/lib/datetime.py @@ -27,8 +27,8 @@ class SimpleHoursPeriod: try: self.timedelta = timedelta(hours=hours) - except OverflowError: - raise ValueError("Time period is too large") + except OverflowError as exc: + raise ValueError("Time period is too large") from exc @classmethod def from_short_form(cls, short_form: str) -> SimpleHoursPeriod: diff --git a/tildes/tildes/metrics.py b/tildes/tildes/metrics.py index 2002db5..5b8dcd2 100644 --- a/tildes/tildes/metrics.py +++ b/tildes/tildes/metrics.py @@ -62,8 +62,8 @@ def incr_counter(name: str, amount: int = 1, **labels: str) -> None: """Increment a Prometheus counter.""" try: counter = _COUNTERS[name] - except KeyError: - raise ValueError("Invalid counter name") + except KeyError as exc: + raise ValueError("Invalid counter name") from exc if labels: counter = counter.labels(**labels) @@ -75,8 +75,8 @@ def get_histogram(name: str, **labels: str) -> Histogram: """Return an (optionally labeled) Prometheus histogram by name.""" try: hist = _HISTOGRAMS[name] - except KeyError: - raise ValueError("Invalid histogram name") + except KeyError as exc: + raise ValueError("Invalid histogram name") from exc if labels: hist = hist.labels(**labels) @@ -93,8 +93,8 @@ def get_summary(name: str, **labels: str) -> Summary: """Return an (optionally labeled) Prometheus summary by name.""" try: hist = _SUMMARIES[name] - except KeyError: - raise ValueError("Invalid summary name") + except KeyError as exc: + raise ValueError("Invalid summary name") from exc if labels: hist = hist.labels(**labels) diff --git a/tildes/tildes/models/comment/comment_tree.py b/tildes/tildes/models/comment/comment_tree.py index 40be94d..f9ec90d 100644 --- a/tildes/tildes/models/comment/comment_tree.py +++ b/tildes/tildes/models/comment/comment_tree.py @@ -254,7 +254,7 @@ class CommentTree: # if all the top-level comments end up fully collapsed, # uncollapse the ones we just collapsed (so we don't have a # comment page that's all collapsed comments) - if all([comment.collapsed_state == "full" for comment in self.tree]): + if all(comment.collapsed_state == "full" for comment in self.tree): for comment, was_unknown in zip(self.tree, top_unknown_initial): if was_unknown: comment.collapsed_state = None diff --git a/tildes/tildes/models/pagination.py b/tildes/tildes/models/pagination.py index aa92bd4..86312bd 100644 --- a/tildes/tildes/models/pagination.py +++ b/tildes/tildes/models/pagination.py @@ -267,16 +267,16 @@ class MixedPaginatedResults(PaginatedResults): """Merge all the supplied results into a single one.""" sort_column_name = paginated_results[0].query._sort_column.name if any( - [r.query._sort_column.name != sort_column_name for r in paginated_results] + r.query._sort_column.name != sort_column_name for r in paginated_results ): raise ValueError("All results must by sorted by the same column.") reverse_sort = paginated_results[0].query.sort_desc - if any([r.query.sort_desc != reverse_sort for r in paginated_results]): + if any(r.query.sort_desc != reverse_sort for r in paginated_results): raise ValueError("All results must by sorted in the same direction.") is_query_reversed = paginated_results[0].query.is_reversed - if any([r.query.is_reversed != is_query_reversed for r in paginated_results]): + if any(r.query.is_reversed != is_query_reversed for r in paginated_results): raise ValueError("All results must have the same directionality.") # merge all the results into one list and sort it @@ -288,8 +288,8 @@ class MixedPaginatedResults(PaginatedResults): self.per_page = min([r.per_page for r in paginated_results]) - self.has_prev_page = any([r.has_prev_page for r in paginated_results]) - self.has_next_page = any([r.has_next_page for r in paginated_results]) + self.has_prev_page = any(r.has_prev_page for r in paginated_results) + self.has_next_page = any(r.has_next_page for r in paginated_results) if len(self.results) > self.per_page: if is_query_reversed: diff --git a/tildes/tildes/models/topic/topic.py b/tildes/tildes/models/topic/topic.py index 143ec60..6e9435b 100644 --- a/tildes/tildes/models/topic/topic.py +++ b/tildes/tildes/models/topic/topic.py @@ -191,7 +191,7 @@ class Topic(DatabaseModel): important_tags = set(self.group.important_topic_tags + global_important_tags) # used with startswith() to check for sub-tags - important_prefixes = tuple([f"{tag}." for tag in important_tags]) + important_prefixes = tuple(f"{tag}." for tag in important_tags) return [ tag diff --git a/tildes/tildes/request_methods.py b/tildes/tildes/request_methods.py index 7274894..8fcfd44 100644 --- a/tildes/tildes/request_methods.py +++ b/tildes/tildes/request_methods.py @@ -45,7 +45,7 @@ def is_bot(request: Request) -> bool: if request.user_agent: return any( - [substring in request.user_agent for substring in bot_user_agent_substrings] + substring in request.user_agent for substring in bot_user_agent_substrings ) return False @@ -83,8 +83,8 @@ def check_rate_limit(request: Request, action_name: str) -> RateLimitResult: if not action: try: action = RATE_LIMITED_ACTIONS[action_name] - except KeyError: - raise ValueError("Invalid action name: %s" % action_name) + except KeyError as exc: + raise ValueError("Invalid action name: %s" % action_name) from exc action.redis = request.redis @@ -136,8 +136,8 @@ def current_listing_base_url( try: base_view_vars = base_vars_by_route[request.matched_route.name] - except KeyError: - raise AttributeError("Current route is not supported.") + except KeyError as exc: + raise AttributeError("Current route is not supported.") from exc query_vars = { key: val for key, val in request.GET.copy().items() if key in base_view_vars @@ -173,8 +173,8 @@ def current_listing_normal_url( try: normal_view_vars = normal_vars_by_route[request.matched_route.name] - except KeyError: - raise AttributeError("Current route is not supported.") + except KeyError as exc: + raise AttributeError("Current route is not supported.") from exc query_vars = { key: val for key, val in request.GET.copy().items() if key in normal_view_vars diff --git a/tildes/tildes/resources/comment.py b/tildes/tildes/resources/comment.py index 45aefa6..dd371e2 100644 --- a/tildes/tildes/resources/comment.py +++ b/tildes/tildes/resources/comment.py @@ -24,8 +24,8 @@ def comment_by_id36(request: Request, comment_id36: str) -> Comment: try: return get_resource(request, query) - except HTTPNotFound: - raise HTTPNotFound("Comment not found (or it was deleted)") + except HTTPNotFound as exc: + raise HTTPNotFound("Comment not found (or it was deleted)") from exc @use_kwargs(CommentSchema(only=("comment_id36",)), location="matchdict") diff --git a/tildes/tildes/resources/topic.py b/tildes/tildes/resources/topic.py index e5e7ac7..522c451 100644 --- a/tildes/tildes/resources/topic.py +++ b/tildes/tildes/resources/topic.py @@ -18,8 +18,8 @@ def topic_by_id36(request: Request, topic_id36: str) -> Topic: """Get a topic specified by {topic_id36} in the route (or 404).""" try: topic_id = id36_to_id(topic_id36) - except ValueError: - raise HTTPNotFound + except ValueError as exc: + raise HTTPNotFound from exc query = ( request.query(Topic) @@ -30,8 +30,8 @@ def topic_by_id36(request: Request, topic_id36: str) -> Topic: try: topic = get_resource(request, query) - except HTTPNotFound: - raise HTTPNotFound("Topic not found (or it was deleted)") + except HTTPNotFound as exc: + raise HTTPNotFound("Topic not found (or it was deleted)") from exc # if there's also a group specified in the route, check that it's the same group as # the topic was posted in, otherwise redirect to correct group diff --git a/tildes/tildes/schemas/fields.py b/tildes/tildes/schemas/fields.py index 684b81f..436fbb9 100644 --- a/tildes/tildes/schemas/fields.py +++ b/tildes/tildes/schemas/fields.py @@ -49,8 +49,8 @@ class Enum(Field): try: return self._enum_class[value.upper()] - except KeyError: - raise ValidationError("Invalid enum member") + except KeyError as exc: + raise ValidationError("Invalid enum member") from exc class ID36(String): @@ -80,8 +80,8 @@ class ShortTimePeriod(Field): try: return SimpleHoursPeriod.from_short_form(value) - except ValueError: - raise ValidationError("Invalid time period") + except ValueError as exc: + raise ValidationError("Invalid time period") from exc def _serialize( self, @@ -197,8 +197,8 @@ class Ltree(Field): try: return sqlalchemy_utils.Ltree(value) - except (TypeError, ValueError): - raise ValidationError("Invalid path") + except (TypeError, ValueError) as exc: + raise ValidationError("Invalid path") from exc class PostType(String): diff --git a/tildes/tildes/schemas/topic.py b/tildes/tildes/schemas/topic.py index af8ef88..289b0f7 100644 --- a/tildes/tildes/schemas/topic.py +++ b/tildes/tildes/schemas/topic.py @@ -112,8 +112,8 @@ class TopicSchema(Schema): for tag in value: try: group_schema.load({"path": tag}) - except ValidationError: - raise ValidationError("Tag %s is invalid" % tag) + except ValidationError as exc: + raise ValidationError("Tag %s is invalid" % tag) from exc @pre_load def prepare_markdown(self, data: dict, many: bool, partial: Any) -> dict: diff --git a/tildes/tildes/scrapers/youtube_scraper.py b/tildes/tildes/scrapers/youtube_scraper.py index e4c506f..c6ac65b 100644 --- a/tildes/tildes/scrapers/youtube_scraper.py +++ b/tildes/tildes/scrapers/youtube_scraper.py @@ -72,8 +72,10 @@ class YoutubeScraper: try: video_data = response.json()["items"][0] - except (KeyError, IndexError): - raise ScraperError(f"No data returned for video with ID {video_id}") + except (KeyError, IndexError) as exc: + raise ScraperError( + f"No data returned for video with ID {video_id}" + ) from exc return ScraperResult(url, ScraperType.YOUTUBE, video_data) diff --git a/tildes/tildes/views/api/web/topic.py b/tildes/tildes/views/api/web/topic.py index 2269c62..2d28557 100644 --- a/tildes/tildes/views/api/web/topic.py +++ b/tildes/tildes/views/api/web/topic.py @@ -185,8 +185,8 @@ def put_tag_topic(request: Request, tags: str, conflict_check: str) -> dict: try: topic.tags = new_tags - except ValidationError: - raise ValidationError({"tags": ["Invalid tags"]}) + except ValidationError as exc: + raise ValidationError({"tags": ["Invalid tags"]}) from exc # if tags weren't changed, don't add a log entry or update page if set(topic.tags) == set(old_tags): diff --git a/tildes/tildes/views/api/web/user.py b/tildes/tildes/views/api/web/user.py index a0bc23c..ef80973 100644 --- a/tildes/tildes/views/api/web/user.py +++ b/tildes/tildes/views/api/web/user.py @@ -387,8 +387,8 @@ def put_filtered_topic_tags(request: Request, tags: str) -> dict: try: schema = TopicSchema(only=("tags",)) result = schema.load({"tags": split_tags}) - except ValidationError: - raise ValidationError({"tags": ["Invalid tags"]}) + except ValidationError as exc: + raise ValidationError({"tags": ["Invalid tags"]}) from exc request.user.filtered_topic_tags = result["tags"] diff --git a/tildes/tildes/views/donate.py b/tildes/tildes/views/donate.py index 96d7743..36881bb 100644 --- a/tildes/tildes/views/donate.py +++ b/tildes/tildes/views/donate.py @@ -51,8 +51,8 @@ def post_donate_stripe( stripe.api_key = request.registry.settings["api_keys.stripe.secret"] publishable_key = request.registry.settings["api_keys.stripe.publishable"] product_id = request.registry.settings["stripe.recurring_donation_product_id"] - except KeyError: - raise HTTPInternalServerError + except KeyError as exc: + raise HTTPInternalServerError from exc incr_counter("donation_initiations", type="stripe") diff --git a/tildes/tildes/views/register.py b/tildes/tildes/views/register.py index d8415c6..bab4a45 100644 --- a/tildes/tildes/views/register.py +++ b/tildes/tildes/views/register.py @@ -86,8 +86,10 @@ def post_register( request.db_session.add(user) try: request.db_session.flush() - except IntegrityError: - raise HTTPUnprocessableEntity("That username has already been registered.") + except IntegrityError as exc: + raise HTTPUnprocessableEntity( + "That username has already been registered." + ) from exc # the flush above will generate the new user's ID, so use that to update the invite # code with info about the user that registered with it diff --git a/tildes/tildes/views/topic.py b/tildes/tildes/views/topic.py index 0892d3e..0d59776 100644 --- a/tildes/tildes/views/topic.py +++ b/tildes/tildes/views/topic.py @@ -118,8 +118,8 @@ def post_group_topics( try: new_topic.tags = tags.split(",") - except ValidationError: - raise ValidationError({"tags": ["Invalid tags"]}) + except ValidationError as exc: + raise ValidationError({"tags": ["Invalid tags"]}) from exc # remove any tag that's the same as the group's name new_topic.tags = [tag for tag in new_topic.tags if tag != str(group.path)]