diff --git a/tildes/requirements-dev.in b/tildes/requirements-dev.in index 0299e91..fca8f73 100644 --- a/tildes/requirements-dev.in +++ b/tildes/requirements-dev.in @@ -10,6 +10,6 @@ pytest-mock testing.redis types-bleach types-python-dateutil -types-redis +types-redis==3.5.4 # Since we can't update the redis package for now we probably also can't update this one types-requests webtest diff --git a/tildes/requirements-dev.txt b/tildes/requirements-dev.txt index 64cd28d..8cd35f1 100644 --- a/tildes/requirements-dev.txt +++ b/tildes/requirements-dev.txt @@ -1,129 +1,134 @@ -ago==0.0.93 -alembic==1.6.5 -appdirs==1.4.4 -argon2-cffi==20.1.0 -astroid==3.3.8 -attrs==24.3.0 -backcall==0.2.0 -beautifulsoup4==4.9.3 +ago==0.1.0 +alembic==1.14.1 +argon2-cffi==25.1.0 +argon2-cffi-bindings==25.1.0 +astroid==3.3.11 +asttokens==3.0.0 +beautifulsoup4==4.13.4 black==25.1.0 -bleach==3.3.1 -certifi==2021.5.30 +bleach==6.2.0 +build==1.3.0 +certifi==2025.8.3 cffi==1.17.1 -charset-normalizer==2.0.3 -click==8.0.1 -cornice==5.2.0 -decorator==5.0.9 +charset-normalizer==3.4.3 +click==8.2.1 +cornice==6.1.0 +decorator==5.2.1 +dill==0.4.0 dodgy==0.2.1 -flake8==7.1.1 +executing==2.2.0 +flake8==7.3.0 flake8-polyfill==1.0.2 -freezegun==1.1.0 -gunicorn==20.1.0 +freezegun==1.5.5 +gitdb==4.0.12 +gitpython==3.1.45 +gunicorn==23.0.0 html5lib==1.1 -html5validator==0.4.0 -hupper==1.10.3 -idna==3.2 -iniconfig==2.0.0 +html5validator==0.4.2 +hupper==1.12.1 +idna==3.10 +iniconfig==2.1.0 invoke==2.2.0 -ipython==7.25.0 -ipython-genutils==0.2.0 -isort==5.9.2 -jedi==0.18.0 -jinja2==3.0.1 -lazy-object-proxy==1.6.0 -lupa==2.4 -mako==1.1.4 -markupsafe==2.0.1 +ipython==9.4.0 +ipython-pygments-lexers==1.1.1 +isort==6.0.1 +jedi==0.19.2 +jinja2==3.1.6 +lupa==2.5 +mako==1.3.10 +markupsafe==3.0.2 marshmallow==3.25.1 -matplotlib-inline==0.1.2 +matplotlib-inline==0.1.7 mccabe==0.7.0 -mypy==1.14.1 -mypy-extensions==1.0.0 -packaging==24.2 -parso==0.8.2 -pastedeploy==2.1.1 -pathspec==0.9.0 -pep517==0.11.0 +mypy==1.17.1 +mypy-extensions==1.1.0 +packaging==25.0 +parso==0.8.4 +pastedeploy==3.1.0 +pathspec==0.12.1 pep8-naming==0.10.0 -pexpect==4.8.0 -pickleshare==0.7.5 -pillow==11.1.0 -pip-tools==6.2.0 -plaster==1.0 -plaster-pastedeploy==0.7 -pluggy==1.5.0 -prometheus-client==0.11.0 -prompt-toolkit==3.0.19 -prospector==1.13.3 +pexpect==4.9.0 +pillow==11.3.0 +pip-tools==7.5.0 +plaster==1.1.2 +plaster-pastedeploy==1.0.1 +platformdirs==4.3.8 +pluggy==1.6.0 +prometheus-client==0.22.1 +prompt-toolkit==3.0.51 +prospector==1.17.2 psycopg2==2.9.10 ptyprocess==0.7.0 publicsuffix2==2.20160818 -py==1.11.0 -pycodestyle==2.12.1 -pycparser==2.20 -pydocstyle==6.1.1 -pyflakes==3.2.0 -pygit2==1.17.0 +pure-eval==0.2.3 +pycodestyle==2.14.0 +pycparser==2.22 +pydocstyle==6.3.0 +pyflakes==3.4.0 +pygit2==1.18.1 pygments==2.9.0 -pylint==3.3.3 -pylint-plugin-utils==0.8.2 -pyotp==2.6.0 -pyparsing==3.2.1 +pylint==3.3.8 +pylint-celery==0.3 +pylint-django==2.6.1 +pylint-plugin-utils==0.9.0 +pyotp==2.9.0 +pyproject-hooks==1.2.0 pyramid==1.10.8 -pyramid-debugtoolbar==4.9 +pyramid-debugtoolbar==4.12.1 pyramid-ipython==0.2 -pyramid-jinja2==2.8 +pyramid-jinja2==2.10.1 pyramid-mako==1.1.0 pyramid-session-redis==1.5.0 -pyramid-tm==2.4 +pyramid-tm==2.6 pyramid-webassets==0.10 -pytest==8.3.4 -pytest-mock==3.14.0 -python-dateutil==2.8.2 -python-editor==1.0.4 +pytest==8.4.1 +pytest-mock==3.14.1 +python-dateutil==2.9.0.post0 pyyaml==6.0.2 -qrcode==7.2 +qrcode==8.2 redis==3.5.3 -regex==2021.7.6 -repoze.lru==0.7 -requests==2.26.0 -requirements-detector==1.3.2 +requests==2.32.4 +requirements-detector==1.4.0 +semver==3.0.4 sentry-sdk==1.3.0 -setoptconf==0.3.0 -six==1.16.0 -snowballstemmer==2.1.0 -soupsieve==2.2.1 +setoptconf-tmp==0.3.1 +six==1.17.0 +smmap==5.0.2 +snowballstemmer==3.0.1 +soupsieve==2.7 sqlalchemy==1.3.24 -sqlalchemy-utils==0.37.8 -stripe==2.60.0 -testing.common.database==2.0.3 -testing.redis==1.1.1 -titlecase==2.3 +sqlalchemy-utils==0.41.2 +stack-data==0.6.3 +stripe==2.6.0 +testing-common-database==2.0.3 +testing-redis==1.1.1 +titlecase==2.4.1 toml==0.10.2 -tomli==1.2.3 -traitlets==5.0.5 -transaction==3.0.1 +tomlkit==0.13.3 +traitlets==5.14.3 +transaction==5.0 translationstring==1.4 -types-bleach==3.3.3 -types-python-dateutil==0.1.4 +types-bleach==6.2.0.20250809 +types-html5lib==1.1.11.20250809 +types-python-dateutil==2.9.0.20250809 types-redis==3.5.4 -types-requests==2.25.0 -typing-extensions==4.12.2 +types-requests==2.32.4.20250809 +typing-extensions==4.14.1 unicodedata2==16.0.0 -urllib3==1.26.6 -venusian==3.0.0 -waitress==2.0.0 -wcwidth==0.2.5 +urllib3==2.5.0 +venusian==3.1.1 +waitress==3.0.2 +wcwidth==0.2.13 webargs==8.0.0 webassets==2.0 webencodings==0.5.1 -webob==1.8.7 -webtest==2.0.35 -wheel==0.36.2 +webob==1.8.9 +webtest==3.0.6 +wheel==0.45.1 wrapt==1.17.2 -zope.deprecation==4.4.0 -zope.interface==5.4.0 -zope.sqlalchemy==1.5 +zope-deprecation==5.1 +zope-interface==7.2 +zope-sqlalchemy==1.5 # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/tildes/requirements.in b/tildes/requirements.in index 5e0c28d..ebe0016 100644 --- a/tildes/requirements.in +++ b/tildes/requirements.in @@ -10,14 +10,14 @@ html5lib invoke ipython lupa -marshmallow +marshmallow==3.25.1 # TODO: Upgrade Marshmallow https://marshmallow.readthedocs.io/en/latest/upgrading.html Pillow pip-tools prometheus-client psycopg2 publicsuffix2==2.20160818 pygit2 -Pygments +Pygments==2.9.0 # TODO: Upgrade Pygments, new version causes an error on posting a code block pyotp pyramid<2.0 pyramid-ipython @@ -28,15 +28,14 @@ pyramid-webassets python-dateutil PyYAML # needs to be installed separately for webassets qrcode -pip-tools -redis +redis==3.5.3 # We can't update redis without also updating pyramid-session-redis requests -sentry-sdk +sentry-sdk==1.3.0 # TODO: Figure out if we can update this SQLAlchemy<1.4 SQLAlchemy-Utils -stripe +stripe==2.6.0 # TODO: Figure out if we can update this titlecase unicodedata2 -webargs +webargs==8.0.0 # TODO: Updating webargs causes an issue with parsing URLs, figure out if we can fix this wrapt -zope.sqlalchemy +zope.sqlalchemy==1.5 # TODO: Figure out if we can update this diff --git a/tildes/requirements.txt b/tildes/requirements.txt index 86922d1..8402774 100644 --- a/tildes/requirements.txt +++ b/tildes/requirements.txt @@ -1,85 +1,87 @@ -ago==0.0.93 -alembic==1.6.5 -argon2-cffi==20.1.0 -backcall==0.2.0 -beautifulsoup4==4.9.3 -bleach==3.3.1 -certifi==2021.5.30 +ago==0.1.0 +alembic==1.14.1 +argon2-cffi==25.1.0 +argon2-cffi-bindings==25.1.0 +asttokens==3.0.0 +beautifulsoup4==4.13.4 +bleach==6.2.0 +build==1.3.0 +certifi==2025.8.3 cffi==1.17.1 -charset-normalizer==2.0.3 -click==8.0.1 -cornice==5.2.0 -decorator==5.0.9 -gunicorn==20.1.0 +charset-normalizer==3.4.3 +click==8.2.1 +cornice==6.1.0 +decorator==5.2.1 +executing==2.2.0 +gunicorn==23.0.0 html5lib==1.1 -hupper==1.10.3 -idna==3.2 +hupper==1.12.1 +idna==3.10 invoke==2.2.0 -ipython==7.25.0 -ipython-genutils==0.2.0 -jedi==0.18.0 -jinja2==3.0.1 -lupa==2.4 -mako==1.1.4 -markupsafe==2.0.1 +ipython==9.4.0 +ipython-pygments-lexers==1.1.1 +jedi==0.19.2 +jinja2==3.1.6 +lupa==2.5 +mako==1.3.10 +markupsafe==3.0.2 marshmallow==3.25.1 -matplotlib-inline==0.1.2 -packaging==24.2 -parso==0.8.2 -pastedeploy==2.1.1 -pep517==0.11.0 -pexpect==4.8.0 -pickleshare==0.7.5 -pillow==11.1.0 -pip-tools==6.2.0 -plaster==1.0 -plaster-pastedeploy==0.7 -prometheus-client==0.11.0 -prompt-toolkit==3.0.19 +matplotlib-inline==0.1.7 +packaging==25.0 +parso==0.8.4 +pastedeploy==3.1.0 +pexpect==4.9.0 +pillow==11.3.0 +pip-tools==7.5.0 +plaster==1.1.2 +plaster-pastedeploy==1.0.1 +prometheus-client==0.22.1 +prompt-toolkit==3.0.51 psycopg2==2.9.10 ptyprocess==0.7.0 publicsuffix2==2.20160818 -pycparser==2.20 -pygit2==1.17.0 +pure-eval==0.2.3 +pycparser==2.22 +pygit2==1.18.1 pygments==2.9.0 -pyotp==2.6.0 -pyparsing==3.2.1 +pyotp==2.9.0 +pyproject-hooks==1.2.0 pyramid==1.10.8 pyramid-ipython==0.2 -pyramid-jinja2==2.8 +pyramid-jinja2==2.10.1 pyramid-session-redis==1.5.0 -pyramid-tm==2.4 +pyramid-tm==2.6 pyramid-webassets==0.10 -python-dateutil==2.8.2 -python-editor==1.0.4 +python-dateutil==2.9.0.post0 pyyaml==6.0.2 -qrcode==7.2 +qrcode==8.2 redis==3.5.3 -requests==2.26.0 +requests==2.32.4 sentry-sdk==1.3.0 -six==1.16.0 -soupsieve==2.2.1 +six==1.17.0 +soupsieve==2.7 sqlalchemy==1.3.24 -sqlalchemy-utils==0.37.8 -stripe==2.60.0 -titlecase==2.3 -tomli==1.2.3 -traitlets==5.0.5 -transaction==3.0.1 +sqlalchemy-utils==0.41.2 +stack-data==0.6.3 +stripe==2.6.0 +titlecase==2.4.1 +traitlets==5.14.3 +transaction==5.0 translationstring==1.4 +typing-extensions==4.14.1 unicodedata2==16.0.0 -urllib3==1.26.6 -venusian==3.0.0 -wcwidth==0.2.5 +urllib3==2.5.0 +venusian==3.1.1 +wcwidth==0.2.13 webargs==8.0.0 webassets==2.0 webencodings==0.5.1 -webob==1.8.7 -wheel==0.36.2 +webob==1.8.9 +wheel==0.45.1 wrapt==1.17.2 -zope.deprecation==4.4.0 -zope.interface==5.4.0 -zope.sqlalchemy==1.5 +zope-deprecation==5.1 +zope-interface==7.2 +zope-sqlalchemy==1.5 # The following packages are considered to be unsafe in a requirements file: # pip diff --git a/tildes/scripts/initialize_db.py b/tildes/scripts/initialize_db.py index 789a2eb..5c6cb84 100644 --- a/tildes/scripts/initialize_db.py +++ b/tildes/scripts/initialize_db.py @@ -8,7 +8,7 @@ import os import subprocess from typing import Optional -from alembic import command # type: ignore[attr-defined] +from alembic import command from alembic.config import Config from sqlalchemy.engine import Connectable, Engine diff --git a/tildes/tests/test_markdown.py b/tildes/tests/test_markdown.py index 74466a3..1335dcf 100644 --- a/tildes/tests/test_markdown.py +++ b/tildes/tests/test_markdown.py @@ -506,3 +506,10 @@ def test_a_rel_kept_user_bio_context(): ) assert "rel=" in processed + + +def test_apply_syntax_highlighting(): + """Ensure syntax highlighting is applied""" + markdown = "```sql\nSELECT * FROM EXAMPLE;```" + processed = convert_markdown_to_safe_html(markdown) + assert 'class="highlight"' in processed diff --git a/tildes/tildes/lib/database.py b/tildes/tildes/lib/database.py index c673776..fae0253 100644 --- a/tildes/tildes/lib/database.py +++ b/tildes/tildes/lib/database.py @@ -7,7 +7,7 @@ import enum from collections.abc import Callable from typing import Any, Optional -from dateutil.rrule import rrule, rrulestr +from dateutil.rrule import rrule, rruleset, rrulestr from pyramid.paster import bootstrap from sqlalchemy import cast, func from sqlalchemy.dialects.postgresql import ARRAY @@ -174,7 +174,7 @@ class RecurrenceRule(TypeDecorator): def process_result_value( self, value: Optional[str], dialect: Dialect - ) -> Optional[rrule]: + ) -> Optional[rrule | rruleset]: """Convert the stored string to an rrule.""" if value is None: return value diff --git a/tildes/tildes/lib/html.py b/tildes/tildes/lib/html.py index 8067ee7..788981d 100644 --- a/tildes/tildes/lib/html.py +++ b/tildes/tildes/lib/html.py @@ -3,7 +3,7 @@ """Functions related to HTML parsing/modification.""" -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, Tag from tildes.lib.string import convert_to_url_slug @@ -15,6 +15,9 @@ def add_anchors_to_headings(html: str) -> str: headings = soup.find_all(["h1", "h2", "h3", "h4", "h5", "h6"]) for heading in headings: + if not isinstance(heading, Tag): + continue + # generate an anchor from the string contents of the heading anchor = convert_to_url_slug("".join(heading.strings)) @@ -28,5 +31,8 @@ def add_anchors_to_headings(html: str) -> str: heading.replace_with(new_heading) + if soup.body is None: + return "" + # html5lib adds and tags around the fragment, strip them back out return "".join([str(tag) for tag in soup.body.children]) diff --git a/tildes/tildes/lib/markdown.py b/tildes/tildes/lib/markdown.py index 506c3f6..3e9d1c7 100644 --- a/tildes/tildes/lib/markdown.py +++ b/tildes/tildes/lib/markdown.py @@ -10,7 +10,7 @@ from random import randint from typing import Any, Optional, Union import bleach -from bs4 import BeautifulSoup +from bs4 import BeautifulSoup, Tag from html5lib.filters.base import Filter from html5lib.treewalkers.base import NonRecursiveTreeWalker from pygments import highlight @@ -270,6 +270,9 @@ def apply_syntax_highlighting(html: str) -> str: # Get all code blocks and for every code block that has info string code_blocks = soup.find_all("code", class_=re.compile("^language-")) for code_block in code_blocks: + if not isinstance(code_block, Tag): + continue + language = code_block["class"][0].replace("language-", "") try: diff --git a/tildes/tildes/models/user/user.py b/tildes/tildes/models/user/user.py index b6221c7..fb40164 100644 --- a/tildes/tildes/models/user/user.py +++ b/tildes/tildes/models/user/user.py @@ -177,7 +177,7 @@ class User(DatabaseModel): def __init__(self, username: str, password: str): """Create a new user account.""" self.username = username - self.password = password # type: ignore + self.password = password def __acl__(self) -> AclType: """Pyramid security ACL.""" @@ -266,7 +266,7 @@ class User(DatabaseModel): raise ValueError("New password is the same as old password") # disable mypy on this line because it doesn't handle setters correctly - self.password = new_password # type: ignore + self.password = new_password def is_correct_two_factor_code(self, code: str) -> bool: """Verify that a TOTP/backup code is correct."""