Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 15 additions & 18 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ on:
- '.flaskenv'
- '.testenv'
- 'package-lock.json'
- 'requirements/base.txt'
- 'requirements/test.txt'
- 'uv.lock'
- '.github/workflows/pytest.yml'

permissions:
Expand All @@ -37,7 +36,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest] # TODO: Figure out macos-latest and Docker
python-version: ['3.11', '3.12']
python-version: ['3.11', '3.12', '3.13', '3.14']

services:
redis:
Expand All @@ -63,24 +62,24 @@ jobs:
uses: docker-practice/actions-setup-docker@1.0.11
if: ${{ matrix.os == 'macos-latest' }}
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v5
- name: Install Chrome (for browser testing)
uses: browser-actions/setup-chrome@latest
- name: Install Chromedriver (for browser testing)
uses: nanasess/setup-chromedriver@v2
- name: Install Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: 'requirements/*.txt'
- name: Cache python packages
uses: actions/cache@v3
cache-dependency-path: 'uv.lock'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
path: ${{ env.pythonLocation }}
key: ${{ matrix.os }}-${{ env.pythonLocation }}-${{ hashFiles('requirements/base.txt') }}-${{ hashFiles('requirements.txt/test.txt') }}
python-version: ${{ matrix.python-version }}
enable-cache: true
- name: Install Python dependencies
run: make install-python-test
run: make install-python-test-github
- name: Install Playwright browser
run: make install-playwright
- name: Install Node
Expand All @@ -107,8 +106,6 @@ jobs:
run: make install-npm
- name: Build Webpack assets
run: make assets
- name: Annotate Pytest failures in PR
run: pip install pytest-github-actions-annotate-failures
- name: Setup hostnames
run: |
sudo -- sh -c "echo '127.0.0.1 funnel.test' >> /etc/hosts"
Expand All @@ -120,23 +117,23 @@ jobs:
psql -h localhost -U postgres -c "create database funnel_testing;"
psql -h localhost -U postgres -c "create database geoname_testing;"
set -a; source .testenv; set +a
FLASK_ENV=testing flask dbconfig | psql -h localhost -U postgres funnel_testing
FLASK_ENV=testing flask dbconfig | psql -h localhost -U postgres geoname_testing
FLASK_ENV=testing uv run --no-dev flask dbconfig | psql -h localhost -U postgres funnel_testing
FLASK_ENV=testing uv run --no-dev flask dbconfig | psql -h localhost -U postgres geoname_testing
psql -h localhost -U postgres -c "grant all privileges on database funnel_testing to $(whoami);"
psql -h localhost -U postgres -c "grant all privileges on database geoname_testing to $(whoami);"
psql -h localhost -U postgres funnel_testing -c "grant all privileges on schema public to $(whoami); grant all privileges on all tables in schema public to $(whoami); grant all privileges on all sequences in schema public to $(whoami);"
psql -h localhost -U postgres geoname_testing -c "grant all privileges on schema public to $(whoami); grant all privileges on all tables in schema public to $(whoami); grant all privileges on all sequences in schema public to $(whoami);"
- name: Test with pytest
run: |
pytest --showlocals --ignore=tests/e2e --cov=funnel
uv run --no-dev pytest --showlocals --ignore=tests/e2e --cov=funnel
- name: Browser tests with pytest
timeout-minutes: 5
run: |
pytest --showlocals --cov-append --cov=funnel tests/e2e
uv run --no-dev pytest --showlocals --cov-append --cov=funnel tests/e2e
- name: Prepare coverage report
run: |
mkdir -p coverage
coverage lcov -o coverage/funnel.lcov
uv run --no-dev coverage lcov -o coverage/funnel.lcov
- name: Upload coverage report to Coveralls
uses: coverallsapp/github-action@master
with:
Expand Down
69 changes: 25 additions & 44 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,62 +6,44 @@ default_language_version:
python: python3.11
ci:
skip: [
'pip-audit',
'uv-lock',
'pysentry',
'creosote',
'no-commit-to-branch',
# 'hadolint-docker',
'docker-compose-check',
# 'docker-compose-check',
'mypy-local', # Runs as a local hook now
]
repos:
- repo: https://github.com/pre-commit-ci/pre-commit-ci-config
rev: v1.6.1
hooks:
- id: check-pre-commit-ci-config
- repo: https://github.com/mxr/sync-pre-commit-deps
rev: v0.0.3
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.18
hooks:
- id: sync-pre-commit-deps
- repo: https://github.com/peterdemin/pip-compile-multi
rev: v3.2.1
- id: uv-lock
- repo: https://github.com/tsvikas/sync-with-uv
rev: v0.5.0
hooks:
- id: pip-compile-multi-verify
files: ^requirements/.*\.(in|txt)$
- repo: https://github.com/pypa/pip-audit
rev: v2.9.0
- id: sync-with-uv
- repo: https://github.com/pysentry/pysentry-pre-commit
rev: v0.3.12
hooks:
- id: pip-audit
args: [
'--disable-pip',
'--no-deps',
'--skip-editable',
'-r',
'requirements/base.txt',
'-r',
'requirements/test.txt',
'-r',
'requirements/dev.txt',
'--ignore-vuln',
'PYSEC-2021-13', # https://github.com/pallets-eco/flask-caching/pull/209
'--ignore-vuln',
'PYSEC-2022-42969', # https://github.com/pytest-dev/pytest/issues/10392
'--ignore-vuln',
'PYSEC-2023-73', # https://github.com/RedisLabs/redisraft/issues/608
]
files: ^requirements/.*\.txt$
- id: pysentry # default pysentry settings
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
rev: v3.21.2
hooks:
- id: pyupgrade
args: ['--keep-runtime-typing', '--py311-plus']
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.0
rev: v0.14.10
hooks:
- id: ruff-check
args: ['--fix', '--exit-non-zero-on-fix']
- id: ruff-format
- repo: https://github.com/PyCQA/pylint
rev: v3.3.7
rev: v4.0.4
hooks:
- id: pylint
args: [
Expand All @@ -73,19 +55,20 @@ repos:
additional_dependencies:
- tomli
- repo: https://github.com/fredrikaverpil/creosote
rev: v4.0.3
rev: v4.1.0
hooks:
- id: creosote
- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.10.0.1
rev: v0.11.0.1
hooks:
- id: shellcheck
args:
- --external-sources
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
- id: check-added-large-files
exclude: ^(uv.lock|package-lock.json)$
- id: check-ast
- id: check-case-conflict
- id: check-docstring-first
Expand All @@ -105,8 +88,6 @@ repos:
- id: detect-private-key
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: fix-encoding-pragma
args: ['--remove']
- id: forbid-new-submodules
- id: mixed-line-ending
- id: name-tests-test
Expand All @@ -124,7 +105,7 @@ repos:
- id: forbid-tabs
- id: remove-tabs
- repo: https://github.com/pycontribs/mirrors-prettier
rev: v3.6.0
rev: v3.6.2
hooks:
- id: prettier
- repo: https://github.com/ducminh-phan/reformat-gherkin
Expand All @@ -141,15 +122,15 @@ repos:
# rev: v2.12.1-beta
# hooks:
# - id: hadolint-docker
- repo: https://github.com/IamTheFij/docker-pre-commit
rev: v3.0.1
hooks:
- id: docker-compose-check
# - repo: https://github.com/IamTheFij/docker-pre-commit
# rev: v3.0.1
# hooks:
# - id: docker-compose-check
- repo: local
hooks:
- id: mypy-local
name: mypy
entry: .venv/bin/mypy
entry: uv run mypy
args:
- --no-warn-unused-ignores
- --no-warn-redundant-casts
Expand Down
39 changes: 12 additions & 27 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ ifeq ($(shell test -d .venv/bin && echo 1 || echo 0), 1)
endif

all:
@echo "You must have an active Python virtualenv (3.11+) before using any of these."
@echo "You must have `uv` installed and available in the path first."
@echo "See https://docs.astral.sh/uv/getting-started/installation/"
@echo
@echo "For production deployment:"
@echo
Expand Down Expand Up @@ -88,26 +89,10 @@ deps-editable:
done;

deps-python: deps-editable
pip install --upgrade pip pip-tools pip-compile-multi
pip-compile-multi --backtracking --use-cache
uv lock --upgrade

deps-python-noup:
pip-compile-multi --backtracking --use-cache --no-upgrade

deps-python-rebuild: deps-editable
pip-compile-multi --backtracking --live

deps-python-base: deps-editable
pip-compile-multi -t requirements/base.in --backtracking --use-cache

deps-python-test: deps-editable
pip-compile-multi -t requirements/test.in --backtracking --use-cache

deps-python-dev: deps-editable
pip-compile-multi -t requirements/dev.in --backtracking --use-cache

deps-python-verify:
pip-compile-multi verify
uv lock

deps-npm:
npm update
Expand Down Expand Up @@ -151,17 +136,17 @@ install-npm:
install-npm-ci:
npm clean-install

install-python-pip:
pip install --upgrade pip
install-python-dev: deps-editable
uv sync --dev

install-python-dev: install-python-pip deps-editable
pip install --use-pep517 -r requirements/dev.txt
install-python-test: deps-editable
uv sync --locked --no-dev --group test

install-python-test: install-python-pip deps-editable
pip install --use-pep517 -r requirements/test.txt
install-python-test-github: deps-editable
uv sync --locked --no-dev --group test-github

install-python: install-python-pip deps-editable
pip install --use-pep517 -r requirements/base.txt
install-python: deps-editable
uv sync --locked --no-dev --no-default-groups

install-playwright:
@if command -v playwright > /dev/null; then\
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Hasgeek

Code for Hasgeek.com at https://hasgeek.com/

Copyright © 2010-2023 by Hasgeek
Copyright © 2010-2025 by Hasgeek

This code is open source under the AGPL v3 license (see LICENSE.txt). We welcome your examination of our code to:

Expand Down
11 changes: 11 additions & 0 deletions funnel/models/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,16 @@ def decorator(temp_cls: type) -> ReopenedType:
'__getattr__',
'__setattr__',
'__delattr__',
'__type_params__', # Python 3.12
}.intersection(set(temp_cls.__dict__.keys())):
raise TypeError("Reopened class contains unsupported __dunder__ attributes")
if '__static_attributes__' in temp_cls.__dict__:
# Merge static attributes (Python 3.13+)
cls.__static_attributes__ = tuple( # type: ignore[attr-defined]
set(cls.__dict__.get('__static_attributes__', ()))
| set(temp_cls.__dict__['__static_attributes__'])
)

if '__annotations__' in temp_cls.__dict__:
# Temp class annotations must be un-stringified as they may refer to names
# not available in the reopened class's namespace
Expand All @@ -328,6 +336,9 @@ def decorator(temp_cls: type) -> ReopenedType:
'__module__',
'__weakref__',
'__annotations__',
'__annotate__', # Python 3.14
'__firstlineno__', # Python 3.13
'__static_attributes__', # Python 3.13
):
# Refuse to overwrite existing attributes
if hasattr(cls, attr):
Expand Down
2 changes: 1 addition & 1 deletion funnel/models/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ def __init__(self, **kwargs: Any) -> None:
# MARK: Notification models ------------------------------------------------------------


class NotificationType(Generic[_D, _F], Protocol):
class NotificationType(Protocol, Generic[_D, _F]):
"""Protocol for :class:`Notification` and :class:`PreviewNotification`."""

preference_context: ClassVar[Any]
Expand Down
2 changes: 1 addition & 1 deletion funnel/models/shortlink.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ def new(
cls,
url: str | furl,
*,
name: Literal[None] = None,
name: None = None,
shorter: bool = False,
reuse: Literal[True] = True,
actor: Account | None = None,
Expand Down
2 changes: 1 addition & 1 deletion funnel/utils/jinja_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def jinja_global(*, init: Literal[False] = False) -> Any: # noqa: ARG001
return ...


def jinja_undefined(*, default: Literal[None] = None) -> Any: # noqa: ARG001
def jinja_undefined(*, default: None = None) -> Any: # noqa: ARG001
"""Sentinel for an optional Jinja2 context variable."""
return ...

Expand Down
Loading
Loading