Skip to content

Commit

Permalink
Merge branch 'flask_23' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjuhrich committed Aug 14, 2023
2 parents 12d9d34 + 2cf549f commit b24566a
Show file tree
Hide file tree
Showing 19 changed files with 60 additions and 73 deletions.
2 changes: 1 addition & 1 deletion deps/wtforms-widgets
2 changes: 1 addition & 1 deletion ldap_sync/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def try_setup_sentry() -> None:
dsn=dsn,
integrations=[logging_integration],
traces_sample_rate=1.0,
) # type: ignore
)


def trigger_sentry() -> typing.NoReturn:
Expand Down
2 changes: 1 addition & 1 deletion ldap_sync/record_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def diff_records(current: T | None, desired: T | None) -> action.Action:
case (c, d):
raise TypeError(f"Cannot diff {type(c).__name__} and {type(d).__name__}")
# see https://github.com/python/mypy/issues/12534
assert False # pragma: no cover
raise AssertionError # pragma: no cover


TKey = typing.TypeVar("TKey")
Expand Down
2 changes: 1 addition & 1 deletion ldap_sync/sources/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@

def establish_and_return_session(connection_string: str) -> Session:
engine = create_engine(connection_string)
set_scoped_session(typing.cast(Session, scoped_session(sessionmaker(bind=engine))))
set_scoped_session(scoped_session(sessionmaker(bind=engine)))
return typing.cast(Session, global_session) # from pycroft.model.session


Expand Down
2 changes: 1 addition & 1 deletion pycroft/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ def _get_config():
return config


config: Config = typing.cast(Config, LocalProxy(_get_config, "config"))
config: Config = typing.cast(Config, LocalProxy(_get_config))
16 changes: 10 additions & 6 deletions pycroft/model/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
:copyright: (c) 2011 by AG DSN.
"""
from typing import overload, TypeVar, Callable, Any, TYPE_CHECKING, cast
import typing as t
from typing import overload, TypeVar, Callable, Any, TYPE_CHECKING

from sqlalchemy.orm import scoped_session
from werkzeug.local import LocalProxy
import wrapt

Expand All @@ -30,8 +32,10 @@ def remove(self):
pass


Session = LocalProxy(lambda: NullScopedSession())
session: orm.Session = cast(orm.Session, LocalProxy(lambda: Session()))
Session: scoped_session[orm.Session] = t.cast(
scoped_session[orm.Session], LocalProxy(lambda: NullScopedSession())
)
session: orm.Session = t.cast(orm.Session, LocalProxy(lambda: Session()))

if TYPE_CHECKING:
def session():
Expand All @@ -45,10 +49,10 @@ def session():
)


def set_scoped_session(scoped_session: orm.Session) -> None:
def set_scoped_session(scoped_session: scoped_session[orm.Session]) -> None:
Session.remove()
# noinspection PyCallByClass
object.__setattr__(Session, '_LocalProxy__local', lambda: scoped_session)
object.__setattr__(Session, "_LocalProxy__wrapped", lambda: scoped_session)
object.__setattr__(Session, "_get_current_object", lambda: scoped_session)


F = TypeVar('F', bound=Callable[..., Any])
Expand Down
16 changes: 11 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ filterwarnings = [
"ignore::DeprecationWarning:kombu.utils.compat",
# actually caused by reportlab.
"ignore:.*load_module.*:DeprecationWarning:importlib._bootstrap",
# fixed in v1.1.2 (unreleased):
# https://github.com/wtforms/flask-wtf/blob/main/docs/changes.rst#version-112
"ignore::DeprecationWarning:flask_wtf.recaptcha.widgets",
# fixed once this PR goes through: https://github.com/python-babel/flask-babel/pull/230
"ignore:'locked_cached_property' is deprecated:DeprecationWarning:flask_babel",
"ignore:pkg_resources is deprecated:DeprecationWarning:passlib",

# TODO this probably comes from the `sphinxcontrib` packages.
# however, e.g. `sphinxcontrib-fulltoc` is unmaintained (last commit 2017).
# we probably need to start forking before updating to py3.12.
# "error:.*Deprecated call.*:DeprecationWarning:",
]
timeout = "10"

Expand All @@ -88,11 +99,6 @@ target-version = "py310"
exclude = [
"deps/",
"doc/",
# match statement cannot be parsed yet… :/
# see https://github.com/charliermarsh/ruff/issues/282

"ldap_sync/record_diff.py",
"web/blueprints/helpers/exception.py",
]
# to look up the meaning of specific rule IDs, use `ruff rule $id`
select = [
Expand Down
2 changes: 1 addition & 1 deletion requirements.dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mypy>=0.961
celery-types~=0.9.3
types-jsonschema~=4.3.0
types-passlib~=1.7.7
sphinx-paramlinks~=0.5.4
sphinx-paramlinks~=0.6.0
sphinxcontrib-fulltoc~=1.2.0
sphinxcontrib-httpdomain~=1.8.0
ruff~=0.0.241
Expand Down
14 changes: 7 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
babel>=2.12.1
Click~=8.0
#MySQL-python==1.2.5
Flask-Babel~=2.0.0
Flask-Babel~=3.1.0
Flask-Login~=0.6.2
Flask-Testing~=0.8.1
Flask-WTF~=1.0.0
Flask-WTF~=1.1.1
Flask-RESTful~=0.3.7
Flask~=2.0.0
Flask~=2.3.2
Jinja2~=3.0
MarkupSafe~=2.0
SQLAlchemy>=2.0.1
WTForms~=2.2.1
Werkzeug~=2.0.0
WTForms~=2.3.3
Werkzeug~=2.3.6
blinker~=1.4
ipaddr~=2.2.0
jsonschema~=3.2.0
Expand All @@ -33,9 +33,9 @@ uwsgi~=2.0.21
uwsgitop~=0.11
GitPython~=2.1.7
mac-vendor-lookup~=0.1.11
marshmallow~=3.11.1
marshmallow~=3.20.1
email-validator~=1.1.1
sentry-sdk[Flask]~=1.0.0
sentry-sdk[Flask]~=1.29.2
-e ./deps/wtforms-widgets/
python-dotenv~=0.21.0
pydantic~=2.0.0
13 changes: 9 additions & 4 deletions scripts/server_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from typing import Callable

from babel.support import Translations
from flask import _request_ctx_stack, g, request
from flask import g, request
from flask.globals import request_ctx
from sqlalchemy.orm import scoped_session, sessionmaker
from werkzeug.middleware.profiler import ProfilerMiddleware

Expand Down Expand Up @@ -71,11 +72,15 @@ def time_response(exception=None):
connection, engine = try_create_connection(connection_string, wait_for_db, app.logger,
args.profile)

set_scoped_session(scoped_session(sessionmaker(bind=engine),
scopefunc=lambda: _request_ctx_stack.top))
set_scoped_session(
scoped_session(
sessionmaker(bind=engine),
scopefunc=lambda: request_ctx._get_current_object(),
)
)

def lookup_translation():
ctx = _request_ctx_stack.top
ctx = request_ctx
if ctx is None:
return None
translations = getattr(ctx, 'pycroft_translations', None)
Expand Down
13 changes: 2 additions & 11 deletions tests/frontend/assertions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import contextlib
import re
import typing as t
from urllib.parse import urlparse, urljoin

import flask.testing
import jinja2 as j
Expand Down Expand Up @@ -60,14 +59,6 @@ def assert_ok(self, endpoint: str, **kw) -> Response:
__tracebackhide__ = True
return self.assert_response_code(endpoint, code=200, **kw)

def fully_qualify_location(self, location: str) -> str:
# inspired by `flask_testing.utils`
if urlparse(location).netloc:
return location

server_name = self.application.config.get('SERVER_NAME') or 'localhost'
return urljoin(f"http://{server_name}", location)

def assert_url_redirects(
self, url: str, expected_location: str | None = None, method: str = "GET", **kw
) -> Response:
Expand All @@ -76,7 +67,7 @@ def assert_url_redirects(
assert 300 <= resp.status_code < 400, \
f"Expected {url!r} to redirect, got status {resp.status}"
if expected_location is not None:
assert resp.location == self.fully_qualify_location(expected_location)
assert resp.location == expected_location
return resp

def assert_redirects(
Expand All @@ -91,7 +82,7 @@ def assert_redirects(
assert 300 <= resp.status_code < 400, \
f"Expected endpoint {endpoint} to redirect, got status {resp.status}"
if expected_location is not None:
assert resp.location == self.fully_qualify_location(expected_location)
assert resp.location == expected_location
return resp

def assert_url_forbidden(self, url: str, method: str = "HEAD", **kw) -> Response:
Expand Down
4 changes: 2 additions & 2 deletions tests/frontend/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import typing as t

import pytest
from flask import _request_ctx_stack
from flask.globals import request_ctx
from sqlalchemy.orm import Session

from pycroft import Config
Expand Down Expand Up @@ -74,7 +74,7 @@ def config(module_session: Session) -> Config:
def blueprint_urls(app: PycroftFlask) -> BlueprintUrls:
def _blueprint_urls(blueprint_name: str) -> list[str]:
return [
_build_rule(_request_ctx_stack.top.url_adapter, rule)
_build_rule(request_ctx.url_adapter, rule)
for rule in app.url_map.iter_rules()
if rule.endpoint.startswith(f"{blueprint_name}.")
]
Expand Down
5 changes: 2 additions & 3 deletions tests/frontend/test_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,12 @@ class TestPropertiesFrontend:
def test_property_gets_added(self, client: TestClient):
group_name = "This is my first property group"
# first time: a redirect
response = client.assert_response_code(
response = client.assert_redirects(
"properties.property_group_create",
code=302,
method="post",
data={"name": group_name},
expected_location=url_for("properties.property_groups"),
)
assert response.location == url_for('properties.property_groups', _external=True)
response = client.assert_url_response_code(response.location, code=200)

content = response.data.decode('utf-8')
Expand Down
4 changes: 1 addition & 3 deletions web/blueprints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
import typing as t
from typing import NoReturn

from flask import Blueprint
from flask import Blueprint, abort
from werkzeug import Response
from werkzeug.utils import redirect

from ..type_utils import abort


def bake_endpoint(blueprint: Blueprint, fn: t.Callable) -> str:
return f"{blueprint.name}.{fn.__name__}"
Expand Down
9 changes: 4 additions & 5 deletions web/blueprints/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import typing as t
from itertools import chain
from flask.globals import current_app
from flask import request, Blueprint
from flask import request, Blueprint, abort
from flask_login import current_user
from werkzeug.wrappers import BaseResponse
from werkzeug import Response

from web.blueprints import bake_endpoint
from ..type_utils import abort


TFun = t.TypeVar("TFun", bound=t.Callable)
Expand Down Expand Up @@ -84,9 +83,9 @@ def decorator(f: TFun) -> TFun:
return f
return decorator

def _check_access(self) -> BaseResponse | None:
def _check_access(self) -> Response | None:
if not current_user.is_authenticated:
return t.cast(BaseResponse, current_app.login_manager.unauthorized()) # type: ignore[attr-defined]
return t.cast(Response, current_app.login_manager.unauthorized()) # type: ignore[attr-defined]
endpoint = request.endpoint
properties = chain(self.required_properties,
self.endpoint_properties_map.get(endpoint, ()))
Expand Down
7 changes: 3 additions & 4 deletions web/blueprints/finance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,7 @@ def transactions_confirm_selected():
Confirms the unconfirmed transactions that where selected by the user in the frontend
Javascript is used to post
"""
if not request.json:
if not request.is_json:
return redirect(url_for(".transactions_unconfirmed"))

ids = request.json.get("ids", [])
Expand Down Expand Up @@ -1055,9 +1055,8 @@ def transaction_delete(transaction_id):
@access.require('finance_show')
@bp.route('/transactions')
def transactions_all():
return render_template('finance/transactions_overview.html',
api_endpoint=url_for(".transactions_all_json",
**request.args))
url = url_for(".transactions_all_json", **request.args) # type: ignore[arg-type]
return render_template("finance/transactions_overview.html", api_endpoint=url)


@access.require('finance_show')
Expand Down
3 changes: 1 addition & 2 deletions web/blueprints/helpers/exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@

from flask import flash, abort, make_response
from flask.typing import ResponseReturnValue
from sqlalchemy.orm import Session, SessionTransaction
from sqlalchemy.orm import SessionTransaction

from pycroft.exc import PycroftException
from pycroft.lib.net import MacExistsException, SubnetFullException
from pycroft.model import session
from pycroft.model.host import MulticastFlagException
from pycroft.model.types import InvalidMACAddressException

Expand Down
4 changes: 2 additions & 2 deletions web/templates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# the Apache License, Version 2.0. See the LICENSE file for details.
from collections import OrderedDict, namedtuple

from flask import _request_ctx_stack
from flask.globals import request_ctx

LinkedScript = namedtuple("LinkedScript", ("url", "mime_type"))
PageResources = namedtuple("PageResources", ("script_files", "ready_scripts",
Expand All @@ -16,7 +16,7 @@ class PageResourceRegistry:
@property
def page_resources(self):
"""Page resources are attached to Flask's current request context."""
ctx = _request_ctx_stack.top
ctx = request_ctx
if not hasattr(ctx, "page_resources"):
ctx.page_resources = PageResources(OrderedDict(), list(),
OrderedDict())
Expand Down
13 changes: 0 additions & 13 deletions web/type_utils.py

This file was deleted.

0 comments on commit b24566a

Please sign in to comment.