Skip to content

Add more type hints #1955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 5, 2025
Merged
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
8 changes: 5 additions & 3 deletions src/moin/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@
name of the authentication method.
"""

from __future__ import annotations

from urllib.parse import quote, quote_plus
from werkzeug.exceptions import abort
from werkzeug.utils import redirect
Expand Down Expand Up @@ -211,9 +213,9 @@ def __init__(self, url):


class BaseAuth:
name = None
login_inputs = []
logout_possible = False
name: str | None = None
login_inputs: list[str] = []
logout_possible: bool = False

def __init__(self, trusted=False, **kw):
self.trusted = trusted
Expand Down
63 changes: 62 additions & 1 deletion src/moin/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,65 @@
# Copyright: 2011-2013 MoinMoin:ThomasWaldmann
# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.

# Nothing to see here any more, please do direct imports from moin.constants.*
from __future__ import annotations

from typing import Any, Protocol, TypedDict
from typing_extensions import TypeAlias

NamespaceMapping: TypeAlias = list[tuple[str, str]]

BackendMapping: TypeAlias = dict[str, Any]

ItemViews: TypeAlias = list[tuple[str, str, str, bool]]

NaviBarEntries: TypeAlias = list[tuple[str, str, dict[str, Any], str, str]]


class AclConfig(TypedDict):
before: str
default: str
after: str
hierarchic: bool


AclMapping: TypeAlias = list[tuple[str, AclConfig]]


class WikiConfigProtocol(Protocol):
wikiconfig_dir: str
instance_dir: str
data_dir: str
index_storage: str
serve_files: dict[str, str]
template_dirs: list[str]
interwikiname: str
interwiki_map: dict[str, str]
sitename: str
edit_locking_policy: str
edit_lock_time: int
expanded_quicklinks_size: int
admin_emails: list[str]
email_tracebacks: bool
registration_only_by_superuser: bool
registration_hint: str
user_email_verification: bool
acl_functions: str
uri: str
namespaces: dict[str, str]
backends: dict[str, str]
acls: dict[str, AclConfig]
namespace_mapping: NamespaceMapping
backend_mapping: BackendMapping
acl_mapping: AclMapping
root_mapping: dict[str, str]
default_root: str
language_default: str
content_dir: str
endpoints_excluded: list[str]
item_views: ItemViews
supplementation_item_names: list[str]
navi_bar: NaviBarEntries
auth_login_inputs: list[str]
auth_have_login: bool
show_hosts: bool
user_homewiki: str
14 changes: 9 additions & 5 deletions src/moin/config/wikiconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@
Done!
"""

from __future__ import annotations

import os

from moin.config.default import DefaultConfig
from moin.utils import get_xstatic_module_path_map
from moin.utils.interwiki import InterWikiMap
Expand All @@ -42,6 +45,7 @@
NAMESPACE_HELP_COMMON,
NAMESPACE_HELP_EN,
)
from moin.config import AclConfig


class Config(DefaultConfig):
Expand Down Expand Up @@ -198,30 +202,30 @@ class Config(DefaultConfig):
# Every user in YOUR-TRUSTED-EDITOR-GROUP will be able to add/delete users.
#
# most wiki data will be stored in NAMESPACE_DEFAULT
NAMESPACE_DEFAULT: dict(
NAMESPACE_DEFAULT: AclConfig(
before="YOUR-SUPER-EDITOR:read,write,create,destroy,admin",
default="YOUR-TRUSTED-EDITORS-GROUP:read,write,create All:read",
after="",
hierarchic=False,
),
# user home pages should be stored here
NAMESPACE_USERS: dict(
NAMESPACE_USERS: AclConfig(
before="YOUR-SUPER-EDITOR:read,write,create,destroy,admin",
default="YOUR-TRUSTED-EDITORS-GROUP:read,write,create All:read",
after="",
# True enables possibility of an admin creating ACL rules for a user's subpages
hierarchic=True,
),
# contains user data that must be kept secret, dis-allow access for all
NAMESPACE_USERPROFILES: dict(before="All:", default="", after="", hierarchic=False),
NAMESPACE_USERPROFILES: AclConfig(before="All:", default="", after="", hierarchic=False),
# editor help namespacess are optional
"help-common": dict(
"help-common": AclConfig(
before="YOUR-SUPER-EDITOR:read,write,create,destroy,admin",
default="YOUR-TRUSTED-EDITORS-GROUP:read,write,create All:read",
after="",
hierarchic=False,
),
"help-en": dict(
"help-en": AclConfig(
before="YOUR-SUPER-EDITOR:read,write,create,destroy,admin",
default="YOUR-TRUSTED-EDITORS-GROUP:read,write,create All:read",
after="",
Expand Down
4 changes: 3 additions & 1 deletion src/moin/constants/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
MoinMoin - misc. constants not fitting elsewhere
"""

from __future__ import annotations

import re

ANON = "anonymous"
Expand All @@ -26,7 +28,7 @@
re.UNICODE | re.VERBOSE,
)

CLEAN_INPUT_TRANSLATION_MAP = {
CLEAN_INPUT_TRANSLATION_MAP: dict[int, str | None] = {
# these chars will be replaced by blanks
ord("\t"): " ",
ord("\r"): " ",
Expand Down
8 changes: 5 additions & 3 deletions src/moin/items/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
Each class in this module corresponds to a contenttype value.
"""

from __future__ import annotations

import os
import time
import uuid
Expand Down Expand Up @@ -178,9 +180,9 @@ class Content:
"""

# placeholder values for registry entry properties
contenttype = None
contenttype: str | None = None
default_contenttype_params = {}
display_name = None
display_name: str | None = None
group = GROUP_OTHER
ingroup_order = 0

Expand All @@ -194,7 +196,7 @@ def create(cls, contenttype, item=None):
logging.debug(f"Content class {content.__class__!r} handles {contenttype!r}")
return content

def __init__(self, contenttype, item=None):
def __init__(self, contenttype: str, item=None):
# We need to keep the exact contenttype since contents may be handled
# by a Content subclass with wildcard contenttype (eg. an unknown
# contenttype some/type gets handled by Binary)
Expand Down
13 changes: 7 additions & 6 deletions src/moin/storage/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@
over key/value pairs
"""

from __future__ import annotations

from moin.constants.namespaces import (
NAMESPACE_DEFAULT,
NAMESPACE_USERPROFILES,
NAMESPACE_USERS,
NAMESPACE_HELP_COMMON,
NAMESPACE_HELP_EN,
)
from moin.config import AclConfig

BACKENDS_PACKAGE = "moin.storage.backends"

Expand All @@ -38,7 +41,7 @@
BACKEND_HELP_EN = "help-en"


def backend_from_uri(uri):
def backend_from_uri(uri: str):
"""
create a backend instance for uri
"""
Expand All @@ -50,17 +53,15 @@ def backend_from_uri(uri):
return module.MutableBackend.from_uri(backend_uri)


def create_mapping(uri, namespaces, backends, acls):
namespace_mapping = namespaces.items()
acl_mapping = acls.items()
def create_mapping(uri: str, namespaces: dict[str, str], backends: dict[str, str], acls: dict[str, AclConfig]):
# TODO "or uri" can be removed in the future, see TODO in config/wikiconfig.py
backend_mapping = [
(backend_name, backend_from_uri((backends[backend_name] or uri) % dict(backend=backend_name, kind="%(kind)s")))
for backend_name in backends
]
# we need the longest mountpoints first, shortest last (-> '' is very last)
namespace_mapping = sorted(namespace_mapping, key=lambda x: len(x[0]), reverse=True)
acl_mapping = sorted(acl_mapping, key=lambda x: len(x[0]), reverse=True)
namespace_mapping = sorted(namespaces.items(), key=lambda x: len(x[0]), reverse=True)
acl_mapping = sorted(acls.items(), key=lambda x: len(x[0]), reverse=True)
return namespace_mapping, dict(backend_mapping), acl_mapping


Expand Down
2 changes: 1 addition & 1 deletion src/moin/storage/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def store(self, meta, data):
"""

@abstractmethod
def remove(self, metaid):
def remove(self, metaid, destroy_data=False):
"""
delete meta, data related to metaid from the backend
"""
26 changes: 18 additions & 8 deletions src/moin/storage/backends/fileserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,15 @@
directory.
"""

from __future__ import annotations

from typing import Any
from typing_extensions import override

import os
import errno
import stat
from io import BytesIO
from io import BytesIO, BufferedReader
from urllib.parse import quote as url_quote
from urllib.parse import unquote as url_unquote

Expand All @@ -33,6 +38,7 @@ class Backend(BackendBase):
exposes part of the filesystem (read-only)
"""

@override
@classmethod
def from_uri(cls, uri):
return cls(uri)
Expand All @@ -43,13 +49,15 @@ def __init__(self, path):
"""
self.path = str(path)

@override
def open(self):
pass

@override
def close(self):
pass

def _mkpath(self, key):
def _mkpath(self, key) -> tuple[str, str]:
"""
key -> itemname, absolute path (strip mtime)
"""
Expand All @@ -68,7 +76,7 @@ def _mkpath(self, key):
relpath = itemname.replace(NAME_SEP, os.sep)
return itemname, os.path.join(self.path, relpath)

def _mkkey(self, path):
def _mkkey(self, path) -> tuple[str, int]:
"""
absolute path -> itemname, mtime
"""
Expand All @@ -94,14 +102,14 @@ def _encode(self, key):
def _decode(self, qkey):
return url_unquote(qkey)

def _get_meta(self, itemname, path):
def _get_meta(self, itemname, path) -> dict[str, Any]:
try:
st = os.stat(path)
except OSError as e:
if e.errno == errno.ENOENT:
raise KeyError(itemname)
raise
meta = {}
meta: dict[str, Any] = {}
meta[NAME] = itemname
meta[MTIME] = int(st.st_mtime) # use int, not float
meta[REVID] = str(self._encode("%s.%d" % (meta[NAME], meta[MTIME])))
Expand All @@ -124,7 +132,7 @@ def _get_meta(self, itemname, path):
meta[SIZE] = size
return meta

def _make_directory_page(self, path):
def _make_directory_page(self, path: str) -> str:
try:
dirs = []
files = []
Expand All @@ -144,7 +152,7 @@ def _make_directory_page(self, path):
content = str(err)
return content

def _get_data(self, itemname, path):
def _get_data(self, itemname: str, path: str) -> BufferedReader | BytesIO:
try:
st = os.stat(path)
if stat.S_ISDIR(st.st_mode):
Expand All @@ -159,6 +167,7 @@ def _get_data(self, itemname, path):
raise KeyError(itemname)
raise

@override
def __iter__(self):
# note: instead of just yielding the relative <path>, yield <path>.<mtime>,
# so if the file is updated, the revid will change (and the indexer's
Expand All @@ -170,7 +179,8 @@ def __iter__(self):
for filename in filenames:
yield self._encode("%s.%d" % self._mkkey(os.path.join(dirpath, filename)))

def retrieve(self, key):
@override
def retrieve(self, key) -> tuple[Any, Any]:
key = self._decode(key)
itemname, path = self._mkpath(key)
meta = self._get_meta(itemname, path)
Expand Down
Loading