diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index bc2c6197b..636d87059 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -9,14 +9,14 @@ jobs: pre-commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.3 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - uses: pre-commit/action@v3.0.0 python-lint: runs-on: ubuntu-latest steps: - name: Check out pycroft and submodules - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: recursive - uses: actions/setup-python@v4 @@ -29,13 +29,21 @@ jobs: run: pip install wheel - name: install pip dependencies run: pip install -r requirements.txt -r requirements.dev.txt + id: pip-install + # now come the tests + - name: Execute ruff + run: ruff check --format=github . + if: success() || steps.pip-install.conclusion == 'success' - name: Execute mypy run: ./scripts/run_mypy.sh + if: success() || steps.pip-install.conclusion == 'success' # docs stuff - name: Build sphinx docs run: make SPHINXOPTS="-EN -w sphinx.log" -C doc html + if: success() || steps.pip-install.conclusion == 'success' - name: Render sphinx warnings as annotations run: python ./scripts/render_sphinx_log.py doc/sphinx.log + if: success() || steps.pip-install.conclusion == 'success' - name: Publish sphinx docs as pages artifact uses: actions/upload-pages-artifact@v1 with: @@ -63,7 +71,7 @@ jobs: npm-build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 16 @@ -77,7 +85,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: 'true' - name: Fix permissions @@ -98,7 +106,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: submodules: 'true' - name: Fix permissions diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9bc5b3be1..bd4439985 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,22 @@ # see https://github.com/topics/pre-commit-hook for more repos: +- repo: https://github.com/akaihola/darker + rev: 1.6.1 + hooks: + - id: darker - repo: https://github.com/asottile/pyupgrade rev: v2.29.0 hooks: - id: pyupgrade args: ["--py310-plus"] +- repo: https://github.com/charliermarsh/ruff-pre-commit + # Ruff version. + rev: 'v0.0.241' + hooks: + - id: ruff - repo: https://github.com/returntocorp/semgrep rev: 'v0.91.0' hooks: - id: semgrep # See semgrep.dev/rulesets to select a ruleset and copy its URL - args: ['--config', 'tools/semgrep.yml', '--metrics=off', '--error', '--skip-unknown-extensions'] + args: ['--config', 'tools/semgrep.yml', '--metrics=off', '--error', '--skip-unknown-extensions'] diff --git a/hades_logs/__init__.py b/hades_logs/__init__.py index 260e82490..8b353da19 100644 --- a/hades_logs/__init__.py +++ b/hades_logs/__init__.py @@ -159,7 +159,9 @@ def _get_extension(): try: return current_app.extensions['hades_logs'] except KeyError: - raise HadesConfigError("No HadesLogs instance registered to current Flask app") + raise HadesConfigError( + "No HadesLogs instance registered to current Flask app" + ) from None -hades_logs: HadesLogs = LocalProxy(_get_extension) +hades_logs: HadesLogs = LocalProxy(_get_extension) diff --git a/hades_logs/parsing.py b/hades_logs/parsing.py index 5ded24cb7..73c6aeabc 100644 --- a/hades_logs/parsing.py +++ b/hades_logs/parsing.py @@ -18,8 +18,10 @@ def parse_vlan(vlan): raise ParsingError(f"VLAN identifier has no Name: {name}") try: taggedness = {'1': "tagged", '2': "untagged"}[prefix] - except KeyError: - raise ParsingError(f"VLAN identifier must start with '1' or '2': {stripped}") + except KeyError as e: + raise ParsingError( + f"VLAN identifier must start with '1' or '2': {stripped}" + ) from e return f"{name} ({taggedness})" diff --git a/helpers/interactive.py b/helpers/interactive.py index aebfc6c4d..1fced664e 100644 --- a/helpers/interactive.py +++ b/helpers/interactive.py @@ -15,7 +15,6 @@ """ import logging -import os from flask import _request_ctx_stack from sqlalchemy.orm import scoped_session, sessionmaker @@ -24,7 +23,6 @@ from scripts.connection import try_create_connection, get_connection_string from pycroft.model._all import * -from pycroft import config connection_string = get_connection_string() diff --git a/ldap_sync/concepts/record.py b/ldap_sync/concepts/record.py index 8a609913d..a88003712 100644 --- a/ldap_sync/concepts/record.py +++ b/ldap_sync/concepts/record.py @@ -8,7 +8,6 @@ from __future__ import annotations -import abc import dataclasses import typing diff --git a/ldap_sync/sources/db.py b/ldap_sync/sources/db.py index dcbd7984e..752be4ded 100644 --- a/ldap_sync/sources/db.py +++ b/ldap_sync/sources/db.py @@ -16,7 +16,7 @@ import typing from typing import NamedTuple -from sqlalchemy import and_, func, select, join, text, literal +from sqlalchemy import and_, func, select, join, literal from sqlalchemy.dialects import postgresql from sqlalchemy.orm import scoped_session, sessionmaker, joinedload, foreign, Session diff --git a/ldap_sync/sources/ldap.py b/ldap_sync/sources/ldap.py index 714aa4ef2..ab4df2d2a 100644 --- a/ldap_sync/sources/ldap.py +++ b/ldap_sync/sources/ldap.py @@ -14,7 +14,6 @@ from ..concepts.record import UserRecord, GroupRecord from ..config import SyncConfig from ..concepts.types import LdapRecord -from ..conversion import ldap_user_to_record, ldap_group_to_record def establish_and_return_ldap_connection(config: SyncConfig) -> ldap3.Connection: diff --git a/pycroft/helpers/i18n/babel.py b/pycroft/helpers/i18n/babel.py index 8cfefc3b6..027089ce0 100644 --- a/pycroft/helpers/i18n/babel.py +++ b/pycroft/helpers/i18n/babel.py @@ -15,8 +15,14 @@ _unspecified_locale = Locale("en", "US") _null_translations = Translations() -_locale_lookup: typing.Callable[[], Locale] = lambda: _unspecified_locale -_translations_lookup: typing.Callable[[], Translations] = lambda: _null_translations + + +def _locale_lookup() -> Locale: + return _unspecified_locale + + +def _translations_lookup() -> Translations: + return _null_translations def get_locale() -> Locale: diff --git a/pycroft/helpers/i18n/formatting.py b/pycroft/helpers/i18n/formatting.py index 1816f3e9a..d7f1577c5 100644 --- a/pycroft/helpers/i18n/formatting.py +++ b/pycroft/helpers/i18n/formatting.py @@ -165,7 +165,7 @@ def format_param(p, options: dict[type, dict[str, typing.Any]]) -> Formattable: raise TypeError( f"No formatter available for type {qualified_typename(concrete_type)}" " or any supertype." - ) + ) from None option_policy: OptionPolicy | None = getattr(formatter, "__option_policy__", None) if option_policy == "ignore": formatter_options: dict[str, typing.Any] = {} diff --git a/pycroft/helpers/i18n/serde.py b/pycroft/helpers/i18n/serde.py index 9e637a52f..fe8d607fd 100644 --- a/pycroft/helpers/i18n/serde.py +++ b/pycroft/helpers/i18n/serde.py @@ -23,8 +23,8 @@ def identity(x): def deserialize_money(v): try: return Money(Decimal(v[0]), v[1]) - except IndexError: - raise ValueError() + except IndexError as e: + raise ValueError from e def serialize_interval(interval: Interval) -> dict[str, typing.Any]: @@ -147,7 +147,7 @@ def serialize_param(param): raise TypeError( "No serialization available for type {} or any" "supertype".format(qualified_typename(concrete_type)) - ) + ) from None return {"type": qualified_typename(type_), "value": serializer(param)} @@ -156,5 +156,5 @@ def deserialize_param(param): try: deserializer = deserialize_map[type_name] except KeyError: - raise TypeError(f"No deserialization available for type {type_name}") + raise TypeError(f"No deserialization available for type {type_name}") from None return deserializer(param["value"]) diff --git a/pycroft/helpers/interval.py b/pycroft/helpers/interval.py index 89b21e0ff..1bd1eecc7 100644 --- a/pycroft/helpers/interval.py +++ b/pycroft/helpers/interval.py @@ -727,7 +727,7 @@ def _complement(intervals: t.Iterable[Interval[T]]) -> t.Iterator[Interval[T]]: yield Interval(Bound(NegativeInfinity, False), ~first.lower_bound) a, b = tee(intervals) last = first - for current_, next_ in zip(chain((first,), a), b): + for current_, next_ in zip(chain((first,), a), b, strict=False): yield Interval(~current_.upper_bound, ~next_.lower_bound) last = next_ if not last.upper_bound.unbounded: diff --git a/pycroft/helpers/printing/__init__.py b/pycroft/helpers/printing/__init__.py index ef00ee9e4..431555ccf 100644 --- a/pycroft/helpers/printing/__init__.py +++ b/pycroft/helpers/printing/__init__.py @@ -135,8 +135,10 @@ def generate_user_sheet( style = getStyleSheet() story = [] - PAGE_WIDTH = defaultPageSize[0] - PAGE_HEIGHT = defaultPageSize[1] + # noinspection Ruff + defaultPageSize[0] + # noinspection Ruff + defaultPageSize[1] # HEADER im_web = Image(ASSETS_WEB_FILENAME, 0.4 * cm, 0.4 * cm) diff --git a/pycroft/lib/facilities.py b/pycroft/lib/facilities.py index ec9dee830..d71012e65 100644 --- a/pycroft/lib/facilities.py +++ b/pycroft/lib/facilities.py @@ -5,7 +5,6 @@ import logging import re import typing as t -from collections import defaultdict from dataclasses import dataclass from itertools import groupby diff --git a/pycroft/lib/finance.py b/pycroft/lib/finance.py index 256dc5c70..44bfb58f9 100644 --- a/pycroft/lib/finance.py +++ b/pycroft/lib/finance.py @@ -23,7 +23,6 @@ text, future from sqlalchemy.orm import aliased, contains_eager, joinedload, Session, Query from sqlalchemy.orm.exc import NoResultFound -from sqlalchemy.sql import Select from pycroft import config from pycroft.helpers.date import diff_month, last_day_of_month @@ -162,7 +161,7 @@ def complex_transaction( def transferred_amount( from_account: Account, to_account: Account, - when: Interval[date] = t.cast(Interval[date], UnboundedInterval), + when: Interval[date] = t.cast(Interval[date], UnboundedInterval), # noqa: B008 ) -> Decimal: """ Determine how much has been transferred from one account to another in a @@ -472,7 +471,7 @@ def is_ordered( except StopIteration: # iterable is empty return True - return all(relation(x, y) for x, y in zip(a, b)) + return all(relation(x, y) for x, y in zip(a, b, strict=False)) @with_transaction @@ -506,9 +505,9 @@ def import_bank_account_activities_csv( process_record(index, record, imported_at=imported_at) for index, record in records) except StopIteration: - raise CSVImportError(gettext("No data present.")) + raise CSVImportError(gettext("No data present.")) from None except csv.Error as e: - raise CSVImportError(gettext("Could not read CSV."), e) + raise CSVImportError(gettext("Could not read CSV."), e) from e if not activities: raise CSVImportError(gettext("No data present.")) if not is_ordered((a[8] for a in activities), operator.ge): @@ -617,7 +616,8 @@ def process_record( "Record {1}: {2}") raw_record = restore_record(record) raise CSVImportError( - message.format(record.our_account_number, index, raw_record), e) + message.format(record.our_account_number, index, raw_record), e + ) from None try: valid_on = datetime.strptime(record.valid_on, "%d.%m.%y").date() @@ -625,15 +625,14 @@ def process_record( except ValueError as e: message = gettext("Illegal date format. Record {1}: {2}") raw_record = restore_record(record) - raise CSVImportError(message.format(index, raw_record), e) + raise CSVImportError(message.format(index, raw_record), e) from e try: amount = Decimal(record.amount.replace(",", ".")) except ValueError as e: message = gettext("Illegal value format {0}. Record {1}: {2}") raw_record = restore_record(record) - raise CSVImportError( - message.format(record.amount, index, raw_record), e) + raise CSVImportError(message.format(record.amount, index, raw_record), e) from e return (amount, bank_account.id, cleanup_description(record.reference), record.reference, record.other_account_number, @@ -897,7 +896,7 @@ def build_transactions_query( ): sort_by = "valid_on" - descending = (sort_order == "desc") ^ (positive == False) + descending = (sort_order == "desc") ^ (positive is False) ordering = sort_by + " desc" if descending else sort_by if search: query = query.filter(Transaction.description.ilike(f'%{search}%')) diff --git a/pycroft/lib/mail.py b/pycroft/lib/mail.py index ea7d70233..03c54cc42 100644 --- a/pycroft/lib/mail.py +++ b/pycroft/lib/mail.py @@ -122,7 +122,7 @@ def send_mails(mails: list[Mail]) -> tuple[bool, int]: 'trace': True, 'data': {'exception_arguments': e.args} }) - raise RetryableException + raise RetryableException from e try: smtp: smtplib.SMTP @@ -143,13 +143,17 @@ def send_mails(mails: list[Mail]) -> tuple[bool, int]: traceback.print_exc() # smtp.connect failed to connect - logger.critical(f'Unable to connect to SMTP server: %s', e, extra={ - 'trace': True, - 'tags': {'mailserver': f"{smtp_host}:{smtp_host}"}, - 'data': {'exception_arguments': e.args} - }) - - raise RetryableException + logger.critical( + "Unable to connect to SMTP server: %s", + e, + extra={ + "trace": True, + "tags": {"mailserver": f"{smtp_host}:{smtp_host}"}, + "data": {"exception_arguments": e.args}, + }, + ) + + raise RetryableException from e else: failures: int = 0 diff --git a/pycroft/lib/membership.py b/pycroft/lib/membership.py index 265139cfa..4564c4827 100644 --- a/pycroft/lib/membership.py +++ b/pycroft/lib/membership.py @@ -75,7 +75,9 @@ def make_member_of( user: User, group: PropertyGroup, processor: User, - during: Interval[DateTimeTz] = t.cast(Interval[DateTimeTz], UnboundedInterval), + during: Interval[DateTimeTz] = t.cast( # noqa: B008 + Interval[DateTimeTz], UnboundedInterval + ), ) -> None: """Makes a user member of a group in a given interval. @@ -116,7 +118,9 @@ def remove_member_of( user: User, group: PropertyGroup, processor: User, - during: Interval[DateTimeTz] = t.cast(Interval[DateTimeTz], UnboundedInterval), + during: Interval[DateTimeTz] = t.cast( # noqa: B008 + Interval[DateTimeTz], UnboundedInterval + ), ) -> None: """Remove a user from a group in a given interval. @@ -185,18 +189,15 @@ def user_memberships_query(user_id: int, active_groups_only: bool = False) -> Qu p_granted = aliased(Property) p_denied = aliased(Property) memberships = ( - memberships - .join(group) - .outerjoin(p_granted, and_(p_granted.property_group_id == group.id, - p_granted.granted == True)) - .add_column(func.array_agg(distinct(p_granted.name)) - .label('granted')) - - .outerjoin(p_denied, and_(p_denied.property_group_id == group.id, - p_denied.granted == False)) - .add_column(func.array_agg(distinct(p_denied.name)) - .label('denied')) - - .group_by(Membership.id) + memberships.join(group) + .outerjoin( + p_granted, and_(p_granted.property_group_id == group.id, p_granted.granted) + ) + .add_column(func.array_agg(distinct(p_granted.name)).label("granted")) + .outerjoin( + p_denied, and_(p_denied.property_group_id == group.id, ~p_denied.granted) + ) + .add_column(func.array_agg(distinct(p_denied.name)).label("denied")) + .group_by(Membership.id) ) return memberships diff --git a/pycroft/lib/task.py b/pycroft/lib/task.py index a593157fe..162f193d2 100644 --- a/pycroft/lib/task.py +++ b/pycroft/lib/task.py @@ -11,10 +11,10 @@ from marshmallow import ValidationError from sqlalchemy import select -from sqlalchemy.orm import with_polymorphic, Session +from sqlalchemy.orm import with_polymorphic from pycroft.helpers.i18n import deferred_gettext -from pycroft.helpers.utc import DateTimeTz, with_min_time, ensure_tz +from pycroft.helpers.utc import DateTimeTz, ensure_tz from pycroft.lib.logging import log_task_event from pycroft.model import session from pycroft.model.session import with_transaction diff --git a/pycroft/lib/user.py b/pycroft/lib/user.py index a0a263885..5fbb44665 100644 --- a/pycroft/lib/user.py +++ b/pycroft/lib/user.py @@ -13,12 +13,11 @@ import re import typing import typing as t -from datetime import datetime, timedelta, date +from datetime import timedelta, date from difflib import SequenceMatcher from typing import Iterable from sqlalchemy import func, select, Boolean, String -from sqlalchemy.engine import Row from pycroft import config, property from pycroft.helpers import user as user_helper, utc @@ -829,7 +828,7 @@ def move_out( deleted_interfaces = list() num_hosts = 0 - for num_hosts, h in enumerate(user.hosts, 1): + for num_hosts, h in enumerate(user.hosts, 1): # noqa: B007 if not h.switch and (h.room == user.room or end_membership): for interface in h.interfaces: deleted_interfaces.append(interface.mac) @@ -843,7 +842,6 @@ def move_out( .format(room=user.room.short_name, num_hosts=num_hosts, interfaces=', '.join(deleted_interfaces)) - had_custom_address = user.has_custom_address user.room = None elif num_hosts: message = "Deleted interfaces {interfaces} of {num_hosts} hosts." \ @@ -947,7 +945,6 @@ def membership_ending_task(user: User) -> UserTask: # Casting jsonb -> bool directly is only supported since PG v11 .filter( UserTask.parameters_json["end_membership"].cast(String).cast(Boolean) - == True ) .order_by(UserTask.due.asc()) .first(), @@ -977,7 +974,7 @@ def membership_beginning_task(user: User) -> UserTask: UserTask.q.filter_by( user_id=user.id, status=TaskStatus.OPEN, type=TaskType.USER_MOVE_IN ) - .filter(UserTask.parameters_json["begin_membership"].cast(Boolean) == True) + .filter(UserTask.parameters_json["begin_membership"].cast(Boolean)) .order_by(UserTask.due.asc()) .first(), ) diff --git a/pycroft/lib/user_deletion.py b/pycroft/lib/user_deletion.py index 5da80a9be..bf831f9dd 100644 --- a/pycroft/lib/user_deletion.py +++ b/pycroft/lib/user_deletion.py @@ -11,7 +11,6 @@ from sqlalchemy import func, nulls_last, and_, not_ from sqlalchemy.future import select from sqlalchemy.orm import joinedload, Session -from sqlalchemy.sql.elements import ClauseElement from sqlalchemy.sql.functions import current_timestamp from pycroft import Config diff --git a/pycroft/model/alembic/env.py b/pycroft/model/alembic/env.py index c93bc5392..a189d772a 100644 --- a/pycroft/model/alembic/env.py +++ b/pycroft/model/alembic/env.py @@ -1,6 +1,6 @@ import os from alembic import context -from sqlalchemy import engine_from_config, pool, create_engine +from sqlalchemy import create_engine from logging.config import fileConfig # this is the Alembic Config object, which provides @@ -9,7 +9,7 @@ try: uri = os.environ['PYCROFT_DB_URI'] except KeyError: - raise ValueError("Please Provide a valid uri in `PYCROFT_DB_URI`.") + raise ValueError("Please Provide a valid uri in `PYCROFT_DB_URI`.") from None engine = create_engine(uri) # Interpret the config file for Python logging. @@ -18,7 +18,7 @@ # add your model's MetaData object here # for 'autogenerate' support -from pycroft.model.base import ModelBase +from pycroft.model.base import ModelBase # noqa: E402 target_metadata = ModelBase.metadata # other values from the config, defined by the needs of env.py, diff --git a/pycroft/model/alembic/versions/20234ac06668_use_tstzrange_for_room_history_entry.py b/pycroft/model/alembic/versions/20234ac06668_use_tstzrange_for_room_history_entry.py index 17affafd5..b2897872e 100644 --- a/pycroft/model/alembic/versions/20234ac06668_use_tstzrange_for_room_history_entry.py +++ b/pycroft/model/alembic/versions/20234ac06668_use_tstzrange_for_room_history_entry.py @@ -69,7 +69,7 @@ def upgrade(): ### # -UNIQUENESS CHECK - op.execute(f'drop function room_history_entry_uniqueness cascade') + op.execute("drop function room_history_entry_uniqueness cascade") # also deletes the trigger ### diff --git a/pycroft/model/alembic/versions/249743eb7b94_add_hostname_column_to_dhcphost_view.py b/pycroft/model/alembic/versions/249743eb7b94_add_hostname_column_to_dhcphost_view.py index afcf258a5..2ae4cbdbf 100644 --- a/pycroft/model/alembic/versions/249743eb7b94_add_hostname_column_to_dhcphost_view.py +++ b/pycroft/model/alembic/versions/249743eb7b94_add_hostname_column_to_dhcphost_view.py @@ -6,7 +6,6 @@ """ from alembic import op -import sqlalchemy as sa # revision identifiers, used by Alembic. @@ -18,28 +17,28 @@ def upgrade(): op.execute(""" - CREATE OR REPLACE VIEW dhcphost AS - SELECT interface.mac AS "Mac", - host(ip.address) AS "IpAddress", - host.name AS "Hostname" - FROM "user" - JOIN current_property ON "user".id = current_property.user_id AND NOT current_property.denied - JOIN host ON "user".id = host.owner_id - JOIN interface ON host.id = interface.host_id - JOIN ip ON interface.id = ip.interface_id - WHERE current_property.property_name::text = 'network_access'::text + CREATE OR REPLACE VIEW dhcphost AS + SELECT interface.mac AS "Mac", + host(ip.address) AS "IpAddress", + host.name AS "Hostname" + FROM "user" + JOIN current_property ON "user".id = current_property.user_id AND NOT current_property.denied + JOIN host ON "user".id = host.owner_id + JOIN interface ON host.id = interface.host_id + JOIN ip ON interface.id = ip.interface_id + WHERE current_property.property_name::text = 'network_access'::text """) def downgrade(): op.execute(""" - CREATE OR REPLACE VIEW dhcphost AS - SELECT interface.mac AS "Mac", - host(ip.address) AS "IpAddress" - FROM "user" - JOIN current_property ON "user".id = current_property.user_id AND NOT current_property.denied - JOIN host ON "user".id = host.owner_id - JOIN interface ON host.id = interface.host_id - JOIN ip ON interface.id = ip.interface_id - WHERE current_property.property_name::text = 'network_access'::text + CREATE OR REPLACE VIEW dhcphost AS + SELECT interface.mac AS "Mac", + host(ip.address) AS "IpAddress" + FROM "user" + JOIN current_property ON "user".id = current_property.user_id AND NOT current_property.denied + JOIN host ON "user".id = host.owner_id + JOIN interface ON host.id = interface.host_id + JOIN ip ON interface.id = ip.interface_id + WHERE current_property.property_name::text = 'network_access'::text """) diff --git a/pycroft/model/base.py b/pycroft/model/base.py index 4cf6e2c5b..dd92734dc 100644 --- a/pycroft/model/base.py +++ b/pycroft/model/base.py @@ -10,7 +10,6 @@ :copyright: (c) 2011 by AG DSN. """ import re -import typing as t import ipaddr from sqlalchemy import String @@ -105,7 +104,7 @@ def __str__(self): T = typing.TypeVar('T', bound='ModelBase') @classmethod - def get(cls: type[T], *a, **kw) -> T: + def get(cls: type[T], *a, **kw) -> T: # noqa: F811 pass diff --git a/pycroft/model/ddl.py b/pycroft/model/ddl.py index 8edb3f2bd..ae9b34690 100644 --- a/pycroft/model/ddl.py +++ b/pycroft/model/ddl.py @@ -560,7 +560,7 @@ def add_view(self, table, view, dialect=None, or_replace=True, if_not_exists=Tru DropView(view, if_exists=True), dialect=dialect) def register(self): - for target, create_ddl, drop_ddl in self.objects: + for target, create_ddl, _drop_ddl in self.objects: sqla_event.listen(target, 'after_create', create_ddl) - for target, create_ddl, drop_ddl in reversed(self.objects): + for target, _create_ddl, drop_ddl in reversed(self.objects): sqla_event.listen(target, 'before_drop', drop_ddl) diff --git a/pycroft/model/facilities.py b/pycroft/model/facilities.py index efbb31424..98089c40e 100644 --- a/pycroft/model/facilities.py +++ b/pycroft/model/facilities.py @@ -78,7 +78,7 @@ class Room(IntegerIdModel): swdd_vo_suchname: Mapped[str | None] connected_patch_ports: Mapped[list[PatchPort]] = relationship( - primaryjoin='and_(PatchPort.room_id == Room.id, PatchPort.switch_port_id != None)', + primaryjoin='and_(PatchPort.room_id == Room.id, PatchPort.switch_port_id.is_not(None))', viewonly=True, ) diff --git a/pycroft/model/finance.py b/pycroft/model/finance.py index 45f321bf9..dd7b7534d 100644 --- a/pycroft/model/finance.py +++ b/pycroft/model/finance.py @@ -20,7 +20,7 @@ from pycroft.helpers.i18n import gettext from pycroft.helpers.interval import closed from pycroft.model import ddl -from pycroft.model.types import Money, DateTimeTz +from pycroft.model.types import Money from .base import IntegerIdModel from .exc import PycroftModelException from .type_aliases import str127, str255, datetime_tz_onupdate diff --git a/pycroft/model/hades.py b/pycroft/model/hades.py index c47af92ab..db78d00a9 100644 --- a/pycroft/model/hades.py +++ b/pycroft/model/hades.py @@ -106,7 +106,7 @@ literal(0).label('Priority'), ]).select_from(User) .outerjoin(network_access_subq, User.id == network_access_subq.c.user_id) - .filter(network_access_subq.c.network_access == None) + .filter(network_access_subq.c.network_access.is_(None)) .join(User.hosts) .join(Host.interfaces) .join(Host.room) diff --git a/pycroft/model/task.py b/pycroft/model/task.py index 416028a78..1453a09ec 100644 --- a/pycroft/model/task.py +++ b/pycroft/model/task.py @@ -16,7 +16,6 @@ from typing import TypeVar, Generic from pycroft.model.base import IntegerIdModel -from pycroft.model.types import DateTimeTz from .task_serialization import UserMoveOutSchema, UserMoveSchema, UserMoveInSchema, TaskParams from .type_aliases import str50 from ..helpers import utc diff --git a/pycroft/model/traffic.py b/pycroft/model/traffic.py index ed362663d..eeb473aac 100644 --- a/pycroft/model/traffic.py +++ b/pycroft/model/traffic.py @@ -5,7 +5,6 @@ pycroft.model.traffic ~~~~~~~~~~~~~~~~~~~~~ """ -import enum import typing as t from sqlalchemy import ( @@ -195,9 +194,9 @@ def traffic_history_query(): ).cte() events_ingress = select(events).where( - or_(events.c.type == 'Ingress', events.c.type == None)).cte() + or_(events.c.type == 'Ingress', events.c.type.is_(None))).cte() events_egress = select(events).where( - or_(events.c.type == 'Egress', events.c.type == None)).cte() + or_(events.c.type == 'Egress', events.c.type.is_(None))).cte() hist = (select(func.coalesce(events_ingress.c.day, events_egress.c.day).label('timestamp'), events_ingress.c.amount.label('ingress'), diff --git a/pycroft/model/user.py b/pycroft/model/user.py index b69f862b5..acaa98f0f 100644 --- a/pycroft/model/user.py +++ b/pycroft/model/user.py @@ -58,7 +58,7 @@ from pycroft.model.base import ModelBase, IntegerIdModel from pycroft.model.exc import PycroftModelException from pycroft.model.facilities import Room -from pycroft.model.types import DateTimeTz, TsTzRange +from pycroft.model.types import TsTzRange from .type_aliases import str255, str40 if t.TYPE_CHECKING: diff --git a/pycroft/model/webstorage.py b/pycroft/model/webstorage.py index ae91d9716..293d788f2 100644 --- a/pycroft/model/webstorage.py +++ b/pycroft/model/webstorage.py @@ -11,7 +11,6 @@ from pycroft.helpers import utc from pycroft.model.base import IntegerIdModel from pycroft.model.session import session -from pycroft.model.types import DateTimeTz class WebStorage(IntegerIdModel): diff --git a/pycroft/task.py b/pycroft/task.py index 89d77b680..f4aafba9a 100644 --- a/pycroft/task.py +++ b/pycroft/task.py @@ -102,7 +102,7 @@ def __init__(self) -> None: raise RuntimeError( "Environment variable PYCROFT_DB_URI must be " "set to an SQLAlchemy connection string." - ) + ) from None self.connection, self.engine = try_create_connection( connection_string, 5, logger=logging.getLogger("tasks"), echo=False diff --git a/pyproject.toml b/pyproject.toml index 2010e281a..0b2a9fa2f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,3 +62,41 @@ filterwarnings = [ "ignore:.*load_module.*:DeprecationWarning:importlib._bootstrap", ] timeout = "10" + +[tool.ruff] +line-length = 100 +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 = [ + "E", + "F", + "B", # flake8-bugbear +] +ignore = [ + "E741", # ambiguous variable names + "E501", # line length violations (for now) +] +unfixable = [ + "B", # fixing bug hazards requires knowledge about the intention + + # We don't want to autofix these: when using `== True|False|None` + # in a sqlalchemy expression, the `is` would actually _not_ be correct! + # the correct workaround is to use the `.is_` / `.is_not` functions + # (or, in the case of bools, don't do a comparison at all or use negation) + "E711", # comparison to None + "E712", # comparison to True/False +] + +[tool.ruff.per-file-ignores] +"__init__.py" = ["E402", "F401"] +"pycroft/model/_all.py" = ["F403"] +"helpers/interactive.py" = ["F403"] diff --git a/requirements.dev.txt b/requirements.dev.txt index 511206c7b..e49841438 100644 --- a/requirements.dev.txt +++ b/requirements.dev.txt @@ -18,3 +18,4 @@ types-passlib~=1.7.7 sphinx-paramlinks~=0.5.4 sphinxcontrib-fulltoc~=1.2.0 sphinxcontrib-httpdomain~=1.8.0 +ruff~=0.0.241 diff --git a/scripts/connection.py b/scripts/connection.py index bf7289029..013078ab0 100644 --- a/scripts/connection.py +++ b/scripts/connection.py @@ -42,6 +42,8 @@ def get_connection_string(): try: connection_string = os.environ['PYCROFT_DB_URI'] except KeyError: - raise RuntimeError("Environment variable PYCROFT_DB_URI must be " - "set to an SQLAlchemy connection string.") + raise RuntimeError( + "Environment variable PYCROFT_DB_URI must be " + "set to an SQLAlchemy connection string." + ) from None return connection_string diff --git a/scripts/render_outdated.py b/scripts/render_outdated.py index ab9a38a61..3d92c2370 100755 --- a/scripts/render_outdated.py +++ b/scripts/render_outdated.py @@ -14,7 +14,7 @@ def md_row(*items: str) -> str: COLS = "current wanted latest dependent".split() # don't care about location if not j: - print(f"### :white_check_mark: All NPM packages up-to-date") + print("### :white_check_mark: All NPM packages up-to-date") exit(0) print(f"### :warning: {len(j)} Outdated NPM packages") print() diff --git a/scripts/server_run.py b/scripts/server_run.py index d98064487..233337e3d 100755 --- a/scripts/server_run.py +++ b/scripts/server_run.py @@ -15,7 +15,6 @@ from sqlalchemy.orm import scoped_session, sessionmaker from werkzeug.middleware.profiler import ProfilerMiddleware -import ldap_sync import pycroft import web from pycroft.helpers.i18n import set_translation_lookup, get_locale diff --git a/tests/conftest.py b/tests/conftest.py index 65be8ab84..7175d063f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,8 +20,10 @@ def engine(): try: uri = os.environ['PYCROFT_DB_URI'] except KeyError: - raise RuntimeError("Environment variable PYCROFT_DB_URI must be " - "set to an SQLalchemy connection string.") + raise RuntimeError( + "Environment variable PYCROFT_DB_URI must be " + "set to an SQLalchemy connection string." + ) from None return create_engine(uri, poolclass=SingletonThreadPool, future=True) diff --git a/tests/factories/user.py b/tests/factories/user.py index 474595891..bdfffcc76 100644 --- a/tests/factories/user.py +++ b/tests/factories/user.py @@ -1,7 +1,6 @@ # Copyright (c) 2016 The Pycroft Authors. See the AUTHORS file. # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details. -import warnings import factory from factory.faker import Faker diff --git a/tests/frontend/conftest.py b/tests/frontend/conftest.py index bf0a7bcc7..b903077dd 100644 --- a/tests/frontend/conftest.py +++ b/tests/frontend/conftest.py @@ -8,7 +8,7 @@ from pycroft import Config from pycroft.model.user import User, PropertyGroup from tests.factories import UserFactory, AdminPropertyGroupFactory, ConfigFactory -from web import make_app, PycroftFlask +from web import PycroftFlask from .assertions import TestClient from .fixture_helpers import login_context, BlueprintUrls, _build_rule, prepare_app_for_testing from ..factories.property import FinancePropertyGroupFactory, MembershipFactory diff --git a/tests/frontend/fixture_helpers.py b/tests/frontend/fixture_helpers.py index 577038cd3..e9d1718f3 100644 --- a/tests/frontend/fixture_helpers.py +++ b/tests/frontend/fixture_helpers.py @@ -44,7 +44,7 @@ def _build_rule(url_adapter, rule) -> str: for k, v in rule._converters.items() } except KeyError as e: - raise AssertionError(f"Cannot create mock argument for {e.args[0]}") + raise AssertionError(f"Cannot create mock argument for {e.args[0]}") from e return url_adapter.build(rule.endpoint, values, 'GET') diff --git a/tests/frontend/test_permissions.py b/tests/frontend/test_permissions.py index beaa0ad9f..8904c9a89 100644 --- a/tests/frontend/test_permissions.py +++ b/tests/frontend/test_permissions.py @@ -6,9 +6,8 @@ from sqlalchemy.orm import Session from pycroft.model.config import Config -from pycroft.model.user import PropertyGroup, User -from tests.factories.property import FinancePropertyGroupFactory, \ - AdminPropertyGroupFactory, MembershipFactory +from pycroft.model.user import User +from tests.factories.property import AdminPropertyGroupFactory from tests.factories.user import UserFactory from web import PycroftFlask from web.template_filters import require diff --git a/tests/helpers/interval/test_interval.py b/tests/helpers/interval/test_interval.py index 1a934b81c..352965ce0 100644 --- a/tests/helpers/interval/test_interval.py +++ b/tests/helpers/interval/test_interval.py @@ -35,7 +35,7 @@ def test_contained(one, other): ]) def test_not_contained(one, other): assert other not in one - assert not (other in one) + assert not (other in one) # noqa def test_begin_greater_than_end(): diff --git a/tests/helpers/test_host.py b/tests/helpers/test_host.py index 2cfb0e1d9..b87da1b0e 100644 --- a/tests/helpers/test_host.py +++ b/tests/helpers/test_host.py @@ -1,7 +1,6 @@ # Copyright (c) 2015 The Pycroft Authors. See the AUTHORS file. # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details. -from dataclasses import dataclass from random import shuffle import ipaddr @@ -78,7 +77,7 @@ def build_full_subnet(interface): def _build(): net = factories.SubnetFactory.build() - for num in range(0, calculate_usable_ips(net)): + for _ in range(0, calculate_usable_ips(net)): ip, _ = get_free_ip((net,)) net.ips.append(IP(address=ip, subnet=net, interface=interface)) return net diff --git a/tests/ldap_sync/conftest.py b/tests/ldap_sync/conftest.py index 642bd5bdf..50a5c391b 100644 --- a/tests/ldap_sync/conftest.py +++ b/tests/ldap_sync/conftest.py @@ -112,7 +112,7 @@ def clean_ldap_base(get_connection, sync_config): }, ) if not result: # pragma: no cover - raise RuntimeError(f"Could not create default password policy", result) + raise RuntimeError("Could not create default password policy", result) _cleanup_conn(conn) diff --git a/tests/ldap_sync/test_ldap_sync.py b/tests/ldap_sync/test_ldap_sync.py index 22ae975a3..3a27e8470 100644 --- a/tests/ldap_sync/test_ldap_sync.py +++ b/tests/ldap_sync/test_ldap_sync.py @@ -421,9 +421,8 @@ def test_mail_deletion(self, conn, session, sync_config, sync_all): modified_user.email = None session.add(modified_user) session.flush() - id = modified_user.id - user_records_to_sync = list( + user_records_to_sync = list( # noqa fetch_db_users( session, sync_config.user_base_dn, diff --git a/tests/lib/infrastructure/test_switch.py b/tests/lib/infrastructure/test_switch.py index 49172cf1e..0eb1a8ba6 100644 --- a/tests/lib/infrastructure/test_switch.py +++ b/tests/lib/infrastructure/test_switch.py @@ -2,10 +2,8 @@ # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details import ipaddr -import pytest from pycroft.lib import infrastructure as infra -from pycroft.model.logging import RoomLogEntry from tests import factories diff --git a/tests/lib/user/test_create_user.py b/tests/lib/user/test_create_user.py index 7ef97c249..c3f52d4c5 100644 --- a/tests/lib/user/test_create_user.py +++ b/tests/lib/user/test_create_user.py @@ -1,5 +1,4 @@ import dataclasses -import operator from datetime import date import pytest diff --git a/tests/model/test_finance.py b/tests/model/test_finance.py index 3ebb52012..a4057ee66 100644 --- a/tests/model/test_finance.py +++ b/tests/model/test_finance.py @@ -4,7 +4,6 @@ from functools import partial import pytest -from psycopg2.errorcodes import INVALID_TEXT_REPRESENTATION from sqlalchemy import select, func, text from sqlalchemy.exc import IntegrityError, DataError diff --git a/tests/model/test_hades.py b/tests/model/test_hades.py index c5c359eb0..fd2aa3dc9 100644 --- a/tests/model/test_hades.py +++ b/tests/model/test_hades.py @@ -85,7 +85,7 @@ def test_radcheck(self, session, user, switch): assert row.NASIPAddress == switch.management_ip assert row.Attribute == "User-Name" assert row.Op == "=*" - assert row.Value == None + assert row.Value is None assert row.Priority == 10 assert {row.NASPortId for row in rows} \ diff --git a/tests/model/test_host.py b/tests/model/test_host.py index 6d575e971..80ec2b1e9 100644 --- a/tests/model/test_host.py +++ b/tests/model/test_host.py @@ -1,7 +1,6 @@ # Copyright (c) 2015 The Pycroft Authors. See the AUTHORS file. # This file is part of the Pycroft project and licensed under the terms of # the Apache License, Version 2.0. See the LICENSE file for details. -import re from itertools import chain import pytest diff --git a/tests/model/test_property.py b/tests/model/test_property.py index 6d9906efb..e0fd26330 100644 --- a/tests/model/test_property.py +++ b/tests/model/test_property.py @@ -233,8 +233,12 @@ def test_user_property_groups(self, session, utcnow, user, property_group1, prop assert res == 2 -h = lambda x: timedelta(hours=x) -d = lambda x: timedelta(days=x) +def h(x): + return timedelta(hours=x) + + +def d(x): + return timedelta(days=x) class Test_Membership: diff --git a/tests/model/test_user.py b/tests/model/test_user.py index ce1b855db..ba2b234f6 100644 --- a/tests/model/test_user.py +++ b/tests/model/test_user.py @@ -49,7 +49,7 @@ def test_set_and_verify_password(self, user, session): # Also, why do we depend on `generate_password` instead of testing it separately? # All of this is very unperformant with little benefit. for length in range(4, 10): - for cnt in range(1, 3): + for _ in range(1, 3): pw = generate_password(length) if pw == password: continue diff --git a/tests/table/test_bootstrap_table.py b/tests/table/test_bootstrap_table.py index 0689b8aac..9e36e5349 100644 --- a/tests/table/test_bootstrap_table.py +++ b/tests/table/test_bootstrap_table.py @@ -16,7 +16,7 @@ def test_instatiation_with_name_and_title_works(self): assert c.name == "test_col" assert c.title == "Test Column" assert c.width == 0 - assert c.cell_style == False + assert c.cell_style is False assert repr(c) == "" def test_instantiation_without_name(self): @@ -295,7 +295,9 @@ def test_table_header_generation(self, header): expected_field_names = ["split1_foo", "split1_bar", "split2_foo", "split2_bar"] observed_attr_strings = (m[0] for m in small_col_matches) - for expected_field_name, attr_string in zip(expected_field_names, observed_attr_strings): + for expected_field_name, attr_string in zip( + expected_field_names, observed_attr_strings, strict=True + ): DATA_FIELD_RE = r'data-field="(\w+)"' observed_field_name = re.search(DATA_FIELD_RE, attr_string).group(1) assert observed_field_name == expected_field_name diff --git a/web/api/v0/__init__.py b/web/api/v0/__init__.py index 31c91fd36..209aa4174 100644 --- a/web/api/v0/__init__.py +++ b/web/api/v0/__init__.py @@ -4,17 +4,14 @@ from flask import jsonify, request, current_app from flask_restful import Api, Resource as FlaskRestfulResource, abort, \ reqparse, inputs -from ipaddr import IPAddress from sqlalchemy.exc import IntegrityError from sqlalchemy import select, func from sqlalchemy.orm import joinedload, selectinload from pycroft.helpers import utc from pycroft.helpers.i18n import Message -from pycroft.lib.finance import build_transactions_query, estimate_balance, \ - get_last_import_date -from pycroft.lib.host import change_mac, host_create, interface_create, \ - host_edit +from pycroft.lib.finance import estimate_balance, get_last_import_date +from pycroft.lib.host import change_mac, host_create, interface_create, host_edit from pycroft.lib.net import SubnetFullException from pycroft.lib.swdd import get_swdd_person_id, get_relevant_tenancies, \ get_first_tenancy_with_room @@ -183,7 +180,7 @@ def post(self, user_id): try: edit_email(user, args.new_email, args.forwarded, processor=user) session.session.commit() - except IllegalEmailError as e: + except IllegalEmailError: abort(400, message='Invalid email address.') return "Email has been changed." diff --git a/web/app.py b/web/app.py index 8217d7d9b..fa0251ad9 100644 --- a/web/app.py +++ b/web/app.py @@ -96,7 +96,7 @@ def make_app(debug=False, hades_logs=True): template_filters.register_filters(app) template_tests.register_checks(app) - babel = Babel(app) + Babel(app) if hades_logs: try: HadesLogs(app) @@ -153,7 +153,7 @@ def debug_sentry(): app.logger.info("An info log for inbetween") app.logger.error("Someone used the debug-sentry endpoint! Also, this is a test error.", extra={'pi': 3.141}) - div_by_zero = 1 / 0 + div_by_zero = 1 / 0 # noqa @app.teardown_request def shutdown_session(exception=None): diff --git a/web/blueprints/access.py b/web/blueprints/access.py index 2a7e41309..6318ec741 100644 --- a/web/blueprints/access.py +++ b/web/blueprints/access.py @@ -67,7 +67,6 @@ def require(self, *required_properties): :param str required_properties: Names of the properties that are required. """ - global_properties = self.required_properties view_properties = required_properties def decorator(f): diff --git a/web/blueprints/facilities/__init__.py b/web/blueprints/facilities/__init__.py index 5dc60cb33..e255a0ef1 100644 --- a/web/blueprints/facilities/__init__.py +++ b/web/blueprints/facilities/__init__.py @@ -9,7 +9,6 @@ :copyright: (c) 2012 by AG DSN. """ from collections import defaultdict -from operator import and_ from flask import (Blueprint, flash, jsonify, render_template, url_for, redirect, request, abort) @@ -365,7 +364,7 @@ def patch_port_edit(switch_room_id, patch_port_id): return redirect(url_for('.room_show', room_id=switch_room_id)) if not patch_port.switch_room == switch_room: - flash(f"Patch-Port ist nicht im Switchraum!", "error") + flash("Patch-Port ist nicht im Switchraum!", "error") return redirect(url_for('.room_show', room_id=switch_room_id)) form = PatchPortForm(switch_room=switch_room.short_name, @@ -421,7 +420,7 @@ def patch_port_delete(switch_room_id, patch_port_id): return redirect(url_for('.room_show', room_id=switch_room_id)) if not patch_port.switch_room == switch_room: - flash(f"Patch-Port ist nicht im Switchraum!", "error") + flash("Patch-Port ist nicht im Switchraum!", "error") return redirect(url_for('.room_show', room_id=switch_room_id)) form = Form() diff --git a/web/blueprints/facilities/address.py b/web/blueprints/facilities/address.py index 8507f0425..56924189b 100644 --- a/web/blueprints/facilities/address.py +++ b/web/blueprints/facilities/address.py @@ -18,8 +18,10 @@ def get_address_entity(type: str) -> InstrumentedAttribute: try: return ADDRESS_ENTITIES[type] except KeyError: - raise ValueError(f"Unknown Address type '{type!r}'." - f" Accepted: {' '.join(ADDRESS_ENTITIES)}") + raise ValueError( + f"Unknown Address type '{type!r}'." + f" Accepted: {' '.join(ADDRESS_ENTITIES)}" + ) from None def address_entity_search_query(query: str, entity: InstrumentedAttribute, session: Session, limit: int): diff --git a/web/blueprints/finance/__init__.py b/web/blueprints/finance/__init__.py index dd6ebfb5d..950970b9b 100644 --- a/web/blueprints/finance/__init__.py +++ b/web/blueprints/finance/__init__.py @@ -62,6 +62,8 @@ from web.template_filters import date_filter, money_filter, datetime_filter from web.template_tests import privilege_check +from . import forms + bp = Blueprint('finance', __name__) access = BlueprintAccess(bp, required_properties=['finance_show']) nav = BlueprintNavigation(bp, "Finanzen", icon='fa-euro-sign', blueprint_access=access) @@ -122,7 +124,7 @@ def actions(activity_id): activity_q = (BankAccountActivity.q .options(joinedload(BankAccountActivity.bank_account)) - .filter(BankAccountActivity.transaction_id == None)) + .filter(BankAccountActivity.transaction_id is None)) return jsonify(items=[{ 'bank_account': activity.bank_account.name, @@ -356,8 +358,7 @@ def bank_account_activities_edit(activity_id): obj=activity, bank_account_name=activity.bank_account.name) if activity.transaction_id: - flash(f"Bankbewegung ist bereits zugewiesen!", - 'warning') + flash("Bankbewegung ist bereits zugewiesen!", "warning") form_args = { 'form': form, @@ -527,7 +528,7 @@ def accounts_list(): accounts_by_type = { t[0]: list(t[1]) for t in groupby( - Account.q.filter_by(legacy=False).outerjoin(User).filter(User.id == None) + Account.q.filter_by(legacy=False).outerjoin(User).filter(User.id.is_(None)) .order_by(Account.type).all(), lambda a: a.type.value ) @@ -913,7 +914,7 @@ def transactions_all_json(): (User.account_id == Split.account_id), isouter=True)) .group_by(Split.transaction_id) - .having(func.bool_and(User.id == None)) + .having(func.bool_and(User.id.is_(None))) .alias("nut")) tid = literal_column("nut.transaction_id") @@ -1227,7 +1228,7 @@ def json_accounts_system(): "account_name": localized(account.name), "account_type": account.type.value } for account in Account.q.outerjoin(User).filter( - and_(User.account == None, + and_(User.account.is_(None), Account.type != AccountType.USER_ASSET) ).all()]) diff --git a/web/blueprints/helpers/fints.py b/web/blueprints/helpers/fints.py index 1d337ee77..2ec9e016c 100644 --- a/web/blueprints/helpers/fints.py +++ b/web/blueprints/helpers/fints.py @@ -24,7 +24,6 @@ def get_filtered_transactions(self, account: SEPAAccount, :return: A tuple with list of mt940.models.Transaction objects and another list with tuples of mt940-data and error messages. """ - with_error = [] with self._get_dialog() as dialog: hkkaz = self._find_highest_supported_command(HKKAZ5, HKKAZ6, HKKAZ7) diff --git a/web/blueprints/task/tables.py b/web/blueprints/task/tables.py index 3c396d49e..616e46265 100644 --- a/web/blueprints/task/tables.py +++ b/web/blueprints/task/tables.py @@ -18,16 +18,15 @@ class TaskTable(BootstrapTable): exception_message = Column(title=None, hide_if=lambda: True) type = Column("Typ", hide_if=lambda: True) - def __init__(self, hidden_columns=[], sort_order='asc', *a, **kw): - table_args = kw.pop('table_args', {}) - table_args.setdefault('data-detail-view', "true") - table_args.setdefault('data-sort-name', "due") - table_args.setdefault('data-sort-order', sort_order) - table_args.setdefault('data-row-style', "table.taskRowFormatter") - table_args.setdefault('data-detail-formatter', - "table.taskDetailFormatter") + def __init__(self, hidden_columns: set[str] = None, sort_order="asc", *a, **kw): + table_args = kw.pop("table_args", {}) + table_args.setdefault("data-detail-view", "true") + table_args.setdefault("data-sort-name", "due") + table_args.setdefault("data-sort-order", sort_order) + table_args.setdefault("data-row-style", "table.taskRowFormatter") + table_args.setdefault("data-detail-formatter", "table.taskDetailFormatter") - if 'user' in hidden_columns: + if hidden_columns and "user" in hidden_columns: self.user.hide_if = lambda: True else: self.user.hide_if = lambda: False diff --git a/web/blueprints/user/__init__.py b/web/blueprints/user/__init__.py index 00c59b2ca..20e15518a 100644 --- a/web/blueprints/user/__init__.py +++ b/web/blueprints/user/__init__.py @@ -899,7 +899,7 @@ def block(user_id): try: during = closedopen(session.utcnow(), ends_at) - blocked_user = lib.user.block( + lib.user.block( user=myUser, reason=form.reason.data, processor=current_user, diff --git a/web/blueprints/user/log.py b/web/blueprints/user/log.py index a58f6de3a..618189f73 100644 --- a/web/blueprints/user/log.py +++ b/web/blueprints/user/log.py @@ -11,7 +11,6 @@ from hades_logs import hades_logs from hades_logs.exc import HadesConfigError, HadesOperationalError, HadesTimeout from pycroft.model import session -from pycroft.model.facilities import Room from pycroft.model.host import SwitchPort from pycroft.model.port import PatchPort from pycroft.model.user import User diff --git a/web/template_filters.py b/web/template_filters.py index 92485acda..6e2f4a171 100644 --- a/web/template_filters.py +++ b/web/template_filters.py @@ -11,7 +11,6 @@ """ import pathlib from cmath import log -from datetime import datetime from itertools import chain from re import sub diff --git a/web/templates/infrastructure/switch_show.html b/web/templates/infrastructure/switch_show.html index 41f0d541e..aa581196f 100644 --- a/web/templates/infrastructure/switch_show.html +++ b/web/templates/infrastructure/switch_show.html @@ -32,7 +32,7 @@
Management IP
{{ switch.management_ip }}
Standort
-
{% if switch.host.room != None %}{{ misc.room_link(switch.host.room) }}{% else %}unbekannt{% endif %}
+
{% if switch.host.room is not None %}{{ misc.room_link(switch.host.room) }}{% else %}unbekannt{% endif %}