Pass all code and format checks #249
31 changed files with 209 additions and 155 deletions
|
|
@ -1,8 +1,5 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import (
|
from typing import Any
|
||||||
Any,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
|
|
||||||
from starlette.status import (
|
from starlette.status import (
|
||||||
HTTP_200_OK,
|
HTTP_200_OK,
|
||||||
|
|
@ -50,7 +47,7 @@ class Format(str, Enum):
|
||||||
ttl = 'ttl'
|
ttl = 'ttl'
|
||||||
|
|
||||||
|
|
||||||
JSON = Union[dict[str, Any], list[Any], str, int, float, None]
|
JSON = dict[str, Any] | list[Any] | str | int | float | None
|
||||||
YAML = JSON
|
YAML = JSON
|
||||||
|
|
||||||
config_file_name = '.dumpthings.yaml'
|
config_file_name = '.dumpthings.yaml'
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,9 @@ config_audit_path = dump_things_private_path / 'config_audit'
|
||||||
config_backend = None
|
config_backend = None
|
||||||
config_audit = None
|
config_audit = None
|
||||||
|
|
||||||
|
CONFIG_VERSION_1_ID = 1
|
||||||
|
CONFIG_VERSION_2_ID = 2
|
||||||
|
|
||||||
|
|
||||||
class StrictModel(BaseModel):
|
class StrictModel(BaseModel):
|
||||||
model_config = ConfigDict(
|
model_config = ConfigDict(
|
||||||
|
|
@ -203,8 +206,8 @@ def get_token_permissions(mode: str) -> TokenPermission:
|
||||||
def get_config_backends(
|
def get_config_backends(
|
||||||
store_path: Path,
|
store_path: Path,
|
||||||
) -> tuple[_RecordDirStore, GitAuditBackend]:
|
) -> tuple[_RecordDirStore, GitAuditBackend]:
|
||||||
global config_audit
|
global config_audit # noqa PLW0603 -- this is cached on the first call
|
||||||
global config_backend
|
global config_backend # noqa PLW0603 -- this is cached on the first call
|
||||||
|
|
||||||
config_path = store_path / config_backend_path
|
config_path = store_path / config_backend_path
|
||||||
if not config_path.exists():
|
if not config_path.exists():
|
||||||
|
|
@ -226,9 +229,10 @@ def get_config_backends(
|
||||||
|
|
||||||
def read_config(
|
def read_config(
|
||||||
store_path: Path,
|
store_path: Path,
|
||||||
|
*,
|
||||||
force_reload: bool = False,
|
force_reload: bool = False,
|
||||||
) -> Configuration:
|
) -> Configuration:
|
||||||
global g_abstract_configuration
|
global g_abstract_configuration # noqa PLW0603 -- this is cached on the first call
|
||||||
|
|
||||||
if not g_abstract_configuration or force_reload:
|
if not g_abstract_configuration or force_reload:
|
||||||
config_backend, _ = get_config_backends(store_path)
|
config_backend, _ = get_config_backends(store_path)
|
||||||
|
|
@ -253,8 +257,6 @@ def read_config(
|
||||||
|
|
||||||
|
|
||||||
def get_config() -> Configuration:
|
def get_config() -> Configuration:
|
||||||
global g_abstract_configuration
|
|
||||||
|
|
||||||
if not g_abstract_configuration:
|
if not g_abstract_configuration:
|
||||||
msg = 'Configuration not yet loaded'
|
msg = 'Configuration not yet loaded'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
@ -265,7 +267,7 @@ def store_config(
|
||||||
store_path,
|
store_path,
|
||||||
config: Configuration,
|
config: Configuration,
|
||||||
):
|
):
|
||||||
global g_abstract_configuration
|
global g_abstract_configuration # noqa PLW0603 -- this function updates a globally referenced instance
|
||||||
|
|
||||||
config_backend, audit_backend = get_config_backends(store_path)
|
config_backend, audit_backend = get_config_backends(store_path)
|
||||||
json_object = config.model_dump(mode='json', exclude_none=True, by_alias=True)
|
json_object = config.model_dump(mode='json', exclude_none=True, by_alias=True)
|
||||||
|
|
@ -310,7 +312,7 @@ def check_label(
|
||||||
collection: str,
|
collection: str,
|
||||||
label: str,
|
label: str,
|
||||||
):
|
):
|
||||||
from dump_things_service.utils import get_on_disk_labels
|
from dump_things_service.utils import get_on_disk_labels # noqa PLC0415 -- global import leads to circular imports
|
||||||
|
|
||||||
"""Check that a label exists in a collection configuration or on disk"""
|
"""Check that a label exists in a collection configuration or on disk"""
|
||||||
if label not in get_config_labels(
|
if label not in get_config_labels(
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,10 @@ import hashlib
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import (
|
||||||
|
UTC,
|
||||||
|
datetime,
|
||||||
|
)
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from threading import (
|
from threading import (
|
||||||
Lock,
|
Lock,
|
||||||
|
|
@ -32,6 +35,8 @@ from dump_things_service.audit import AuditBackend
|
||||||
|
|
||||||
index_file_name = 'gitaudit_index.log'
|
index_file_name = 'gitaudit_index.log'
|
||||||
|
|
||||||
|
GIT_ERROR_UNCLEAN_EXIT = 128
|
||||||
|
|
||||||
|
|
||||||
class FlushingThread(Thread):
|
class FlushingThread(Thread):
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -145,10 +150,12 @@ class GitAuditBackend(AuditBackend):
|
||||||
.splitlines()
|
.splitlines()
|
||||||
)
|
)
|
||||||
# Get the log entry
|
# Get the log entry
|
||||||
log_line = next(filter(
|
log_line = next(
|
||||||
lambda l: not l.startswith('+++') and l.startswith('+'),
|
filter(
|
||||||
|
lambda line: not line.startswith('+++') and line.startswith('+'),
|
||||||
log_diff_lines,
|
log_diff_lines,
|
||||||
))[1:]
|
)
|
||||||
|
)[1:]
|
||||||
log_entry = json.loads(log_line)
|
log_entry = json.loads(log_line)
|
||||||
|
|
||||||
# Get the YAML diff
|
# Get the YAML diff
|
||||||
|
|
@ -161,7 +168,9 @@ class GitAuditBackend(AuditBackend):
|
||||||
.decode()
|
.decode()
|
||||||
.splitlines()
|
.splitlines()
|
||||||
)
|
)
|
||||||
yaml_diff = '\n'.join(filter(lambda l: l != '', yaml_diff_lines)) + '\n'
|
yaml_diff = (
|
||||||
|
'\n'.join(filter(lambda line: line != '', yaml_diff_lines)) + '\n'
|
||||||
|
)
|
||||||
|
|
||||||
# Get the YAML content
|
# Get the YAML content
|
||||||
yaml_content = call_git(
|
yaml_content = call_git(
|
||||||
|
|
@ -233,7 +242,7 @@ class GitAuditBackend(AuditBackend):
|
||||||
committer_id: str,
|
committer_id: str,
|
||||||
author_id: str,
|
author_id: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
time_stamp = datetime.now().isoformat()
|
time_stamp = datetime.now(tz=UTC).isoformat()
|
||||||
entry = {
|
entry = {
|
||||||
'time_stamp': time_stamp,
|
'time_stamp': time_stamp,
|
||||||
'committer_id': committer_id,
|
'committer_id': committer_id,
|
||||||
|
|
@ -262,7 +271,7 @@ class GitAuditBackend(AuditBackend):
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
)
|
)
|
||||||
except CommandError as e:
|
except CommandError as e:
|
||||||
if e.returncode == 128:
|
if e.returncode == GIT_ERROR_UNCLEAN_EXIT:
|
||||||
return b''
|
return b''
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -299,7 +308,7 @@ class GitAuditBackend(AuditBackend):
|
||||||
self,
|
self,
|
||||||
record_id: str,
|
record_id: str,
|
||||||
) -> tuple[str, Path, Path]:
|
) -> tuple[str, Path, Path]:
|
||||||
base = hashlib.sha1(record_id.encode()).hexdigest()
|
base = hashlib.sha1(record_id.encode()).hexdigest() # noqa S324 -- hash is not used for security
|
||||||
dir_1, dir_2, _name = base[0:3], base[3:6], base[6:]
|
dir_1, dir_2, _name = base[0:3], base[3:6], base[6:]
|
||||||
location_dir = Path(dir_1) / Path(dir_2)
|
location_dir = Path(dir_1) / Path(dir_2)
|
||||||
return (
|
return (
|
||||||
|
|
@ -329,7 +338,7 @@ class GitAuditBackend(AuditBackend):
|
||||||
self.repo = Repo(self.path)
|
self.repo = Repo(self.path)
|
||||||
|
|
||||||
if not self.index_path.exists():
|
if not self.index_path.exists():
|
||||||
self._rebuild_index()
|
self.rebuild_index()
|
||||||
|
|
||||||
with open(self.index_path) as f:
|
with open(self.index_path) as f:
|
||||||
self.index = {line.strip() for line in f}
|
self.index = {line.strip() for line in f}
|
||||||
|
|
@ -342,7 +351,7 @@ class GitAuditBackend(AuditBackend):
|
||||||
self.cached_index_entries.append(record_id)
|
self.cached_index_entries.append(record_id)
|
||||||
self.index.add(record_id)
|
self.index.add(record_id)
|
||||||
|
|
||||||
def _rebuild_index(self):
|
def rebuild_index(self):
|
||||||
tree_entries = (
|
tree_entries = (
|
||||||
call_git(
|
call_git(
|
||||||
['ls-tree', '-r', 'master:'],
|
['ls-tree', '-r', 'master:'],
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from dump_things_service.audit.gitaudit import GitAuditBackend
|
||||||
|
|
||||||
def _get_git_log(path: Path) -> list[str]:
|
def _get_git_log(path: Path) -> list[str]:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', '-C', str(path), 'log', '--oneline'],
|
['git', '-C', str(path), 'log', '--oneline'], # noqa S607 -- test run in a controlled environment
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
|
|
@ -25,25 +25,26 @@ def test_gitaudit_basic(tmp_path_factory):
|
||||||
|
|
||||||
record_id = 'test_gitaudit_basic'
|
record_id = 'test_gitaudit_basic'
|
||||||
|
|
||||||
for index in range(4):
|
entry_count = 4
|
||||||
|
for index in range(entry_count):
|
||||||
backend.add_record(
|
backend.add_record(
|
||||||
record={'pid': record_id, 'content': index},
|
record={'pid': record_id, 'content': index},
|
||||||
committer_id=f'committer_{100 + index}@x.org',
|
committer_id=f'committer_{100 + index}@x.org',
|
||||||
author_id=f'author_{index}@y.org',
|
author_id=f'author_{index}@y.org',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check that the log file has 4 entries
|
# Check that the log file has `entry_count` entries
|
||||||
backend.flush()
|
backend.flush()
|
||||||
log_lines = _get_audit_log_lines(backend, record_id)
|
log_lines = _get_audit_log_lines(backend, record_id)
|
||||||
assert len(log_lines) == 4
|
assert len(log_lines) == entry_count
|
||||||
|
|
||||||
# Check that the commit log has 4 + 1 (from `README.txt`) entries
|
# Check that the commit log has `entry_count` + 1 (from `README.txt`) entries
|
||||||
commit_log_lines = _get_git_log(tmp_path)
|
commit_log_lines = _get_git_log(tmp_path)
|
||||||
assert len(commit_log_lines) == 5
|
assert len(commit_log_lines) == entry_count + 1
|
||||||
|
|
||||||
# Check that the changes are reported
|
# Check that the changes are reported
|
||||||
changes = backend.get_audit_log(record_id)
|
changes = backend.get_audit_log(record_id)
|
||||||
assert len(changes) == 4
|
assert len(changes) == entry_count
|
||||||
assert tuple(e[0:2] for e in changes.values()) == tuple(
|
assert tuple(e[0:2] for e in changes.values()) == tuple(
|
||||||
(f'committer_{100 + i}@x.org', f'author_{i}@y.org') for i in range(4)
|
(f'committer_{100 + i}@x.org', f'author_{i}@y.org') for i in range(4)
|
||||||
)
|
)
|
||||||
|
|
@ -67,18 +68,20 @@ def test_gitaudit_identical_change(tmp_path_factory):
|
||||||
author_id='author_b@y.org',
|
author_id='author_b@y.org',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
expected_entries = 1
|
||||||
|
|
||||||
# Check that there is only one entry in the audit log
|
# Check that there is only one entry in the audit log
|
||||||
log_lines = _get_audit_log_lines(backend, record_id)
|
log_lines = _get_audit_log_lines(backend, record_id)
|
||||||
assert len(log_lines) == 1
|
assert len(log_lines) == expected_entries
|
||||||
|
|
||||||
# Check that there are two entries in the commit history, one for the
|
# Check that there are two entries in the commit history, one for the
|
||||||
# `README.txt`-file, one for the log entries.
|
# `README.txt`-file, one for the log entries.
|
||||||
commit_log_lines = _get_git_log(tmp_path)
|
commit_log_lines = _get_git_log(tmp_path)
|
||||||
assert len(commit_log_lines) == 2
|
assert len(commit_log_lines) == expected_entries + 1
|
||||||
|
|
||||||
# Check that the changes are reported
|
# Check that the changes are reported
|
||||||
changes = backend.get_audit_log(record_id)
|
changes = backend.get_audit_log(record_id)
|
||||||
assert len(changes) == 1
|
assert len(changes) == expected_entries
|
||||||
|
|
||||||
|
|
||||||
def test_gitaudit_huge_log(tmp_path_factory):
|
def test_gitaudit_huge_log(tmp_path_factory):
|
||||||
|
|
|
||||||
|
|
@ -209,7 +209,7 @@ class ForgejoAuthenticationSource(AuthenticationSource, MethodCache):
|
||||||
def _get_unit_content(self, team: dict, unit_name: str) -> str:
|
def _get_unit_content(self, team: dict, unit_name: str) -> str:
|
||||||
permissions = team['units_map'].get(unit_name)
|
permissions = team['units_map'].get(unit_name)
|
||||||
if not permissions:
|
if not permissions:
|
||||||
logger.debug(f'no unit `repo.actions` in team {self.team}')
|
logger.debug('no unit `repo.actions` in team %s', self.team)
|
||||||
msg = (
|
msg = (
|
||||||
f'no `repo.{unit_name}`-unit defined for team `{self.team}` in '
|
f'no `repo.{unit_name}`-unit defined for team `{self.team}` in '
|
||||||
f'organization {self.organization}'
|
f'organization {self.organization}'
|
||||||
|
|
@ -221,7 +221,7 @@ class ForgejoAuthenticationSource(AuthenticationSource, MethodCache):
|
||||||
return permissions
|
return permissions
|
||||||
|
|
||||||
def _instance_label(self) -> str:
|
def _instance_label(self) -> str:
|
||||||
return self.instance_id or hashlib.md5(self.api_url.encode()).hexdigest()
|
return self.instance_id or hashlib.md5(self.api_url.encode()).hexdigest() # noqa S324 -- hash not used for cryptography, only for label generation
|
||||||
|
|
||||||
@MethodCache.cache_temporary(duration=60)
|
@MethodCache.cache_temporary(duration=60)
|
||||||
def authenticate(
|
def authenticate(
|
||||||
|
|
@ -229,14 +229,17 @@ class ForgejoAuthenticationSource(AuthenticationSource, MethodCache):
|
||||||
token: str,
|
token: str,
|
||||||
) -> AuthenticationInfo:
|
) -> AuthenticationInfo:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f'starting Forgejo authentication: {self.api_url}, {self.organization}, {self.team}'
|
'starting Forgejo authentication: %s, %s, %s',
|
||||||
|
self.api_url,
|
||||||
|
self.organization,
|
||||||
|
self.team,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_teams = self._get_teams_for_user(token)
|
user_teams = self._get_teams_for_user(token)
|
||||||
logger.debug(f'user_teams: {user_teams}')
|
logger.debug('user_teams: %s', str(user_teams))
|
||||||
|
|
||||||
if self.team not in user_teams:
|
if self.team not in user_teams:
|
||||||
logger.debug(f"{self.team} not in user's teams")
|
logger.debug("%s not in user's teams", self.team)
|
||||||
msg = f'token user is not member of team `{self.team}`'
|
msg = f'token user is not member of team `{self.team}`'
|
||||||
raise RemoteAuthenticationError(
|
raise RemoteAuthenticationError(
|
||||||
status=HTTP_401_UNAUTHORIZED,
|
status=HTTP_401_UNAUTHORIZED,
|
||||||
|
|
@ -257,12 +260,12 @@ class ForgejoAuthenticationSource(AuthenticationSource, MethodCache):
|
||||||
token,
|
token,
|
||||||
self.organization,
|
self.organization,
|
||||||
)
|
)
|
||||||
logger.debug(f'organization_teams: {organization_teams}')
|
logger.debug('organization_teams: %s', str(organization_teams))
|
||||||
|
|
||||||
# Check that the configured team exists
|
# Check that the configured team exists
|
||||||
team = organization_teams.get(self.team)
|
team = organization_teams.get(self.team)
|
||||||
if not team:
|
if not team:
|
||||||
logger.debug(f'{self.team} not in organization teams')
|
logger.debug('%s not in organization teams', self.team)
|
||||||
if self.repository is not None:
|
if self.repository is not None:
|
||||||
msg = f'team `{self.team}` has no access to repository `{self.repository}`'
|
msg = f'team `{self.team}` has no access to repository `{self.repository}`'
|
||||||
else:
|
else:
|
||||||
|
|
@ -276,8 +279,9 @@ class ForgejoAuthenticationSource(AuthenticationSource, MethodCache):
|
||||||
code_permissions = self._get_unit_content(team, 'repo.code')
|
code_permissions = self._get_unit_content(team, 'repo.code')
|
||||||
action_permissions = self._get_unit_content(team, 'repo.actions')
|
action_permissions = self._get_unit_content(team, 'repo.actions')
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f'authentication success, team permissions: {code_permissions}, '
|
'authentication success, team permissions: %s, %s',
|
||||||
f'{action_permissions}'
|
code_permissions,
|
||||||
|
action_permissions,
|
||||||
)
|
)
|
||||||
return AuthenticationInfo(
|
return AuthenticationInfo(
|
||||||
token_permission=self._get_permissions(
|
token_permission=self._get_permissions(
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ class _RecordDirStore(StorageBackend):
|
||||||
def get_records_of_classes(
|
def get_records_of_classes(
|
||||||
self,
|
self,
|
||||||
class_names: list[str],
|
class_names: list[str],
|
||||||
pattern: str | None = None,
|
_pattern: str | None = None,
|
||||||
) -> RecordDirResultList:
|
) -> RecordDirResultList:
|
||||||
return RecordDirResultList().add_info(
|
return RecordDirResultList().add_info(
|
||||||
sorted(
|
sorted(
|
||||||
|
|
@ -184,7 +184,7 @@ class _RecordDirStore(StorageBackend):
|
||||||
|
|
||||||
def get_all_records(
|
def get_all_records(
|
||||||
self,
|
self,
|
||||||
pattern: str | None = None,
|
_pattern: str | None = None,
|
||||||
) -> RecordDirResultList:
|
) -> RecordDirResultList:
|
||||||
return RecordDirResultList().add_info(
|
return RecordDirResultList().add_info(
|
||||||
sorted(
|
sorted(
|
||||||
|
|
|
||||||
|
|
@ -130,9 +130,13 @@ class _SQLiteBackend(StorageBackend):
|
||||||
order_by: Iterable[str] | None = None,
|
order_by: Iterable[str] | None = None,
|
||||||
echo: bool = False,
|
echo: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
assert db_path.is_absolute(), f'db_path not absolute {db_path}'
|
if not db_path.is_absolute():
|
||||||
if db_path.exists():
|
msg = f'db_path not absolute: {db_path}'
|
||||||
assert db_path.is_file(), f'db_path not a file {db_path}'
|
raise ValueError(msg)
|
||||||
|
if db_path.exists() and not db_path.is_file():
|
||||||
|
msg = f'db_path not a file: {db_path}'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
super().__init__(order_by=order_by)
|
super().__init__(order_by=order_by)
|
||||||
self.db_path = db_path
|
self.db_path = db_path
|
||||||
self.perform_file_name_conversion()
|
self.perform_file_name_conversion()
|
||||||
|
|
@ -243,14 +247,14 @@ class _SQLiteBackend(StorageBackend):
|
||||||
class_list = ', '.join(f"'{cn}'" for cn in class_names)
|
class_list = ', '.join(f"'{cn}'" for cn in class_names)
|
||||||
if pattern is None:
|
if pattern is None:
|
||||||
statement = text(
|
statement = text(
|
||||||
'select distinct thing.iri, thing.class_name, thing.sort_key, thing.id '
|
'select distinct thing.iri, thing.class_name, thing.sort_key, thing.id ' # noqa S608 -- all variables are controlled
|
||||||
'from thing '
|
'from thing '
|
||||||
f'where thing.class_name in ({class_list}) '
|
f'where thing.class_name in ({class_list}) '
|
||||||
'ORDER BY thing.sort_key'
|
'ORDER BY thing.sort_key'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
statement = text(
|
statement = text(
|
||||||
'select distinct thing.iri, thing.class_name, thing.sort_key, thing.id '
|
'select distinct thing.iri, thing.class_name, thing.sort_key, thing.id ' # noqa S608 -- all variables are controlled
|
||||||
'from thing, json_tree(thing.object) '
|
'from thing, json_tree(thing.object) '
|
||||||
'where lower(json_tree.value) like lower(:pattern) '
|
'where lower(json_tree.value) like lower(:pattern) '
|
||||||
f'and thing.class_name in ({class_list}) '
|
f'and thing.class_name in ({class_list}) '
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ from fastapi import (
|
||||||
FastAPI,
|
FastAPI,
|
||||||
HTTPException,
|
HTTPException,
|
||||||
)
|
)
|
||||||
|
from fastapi.routing import _IncludedRouter
|
||||||
from pydantic import (
|
from pydantic import (
|
||||||
BaseModel,
|
BaseModel,
|
||||||
TypeAdapter,
|
TypeAdapter,
|
||||||
|
|
@ -96,7 +97,17 @@ def {name}(
|
||||||
format: Format = Format.json,
|
format: Format = Format.json,
|
||||||
) -> JSONResponse | PlainTextResponse:
|
) -> JSONResponse | PlainTextResponse:
|
||||||
logger.info('{name}(%s, %s, %s, %s, %s)', repr(data), repr('{class_name}'), repr({model_var_name}), repr(add_submission_tag), repr(format))
|
logger.info('{name}(%s, %s, %s, %s, %s)', repr(data), repr('{class_name}'), repr({model_var_name}), repr(add_submission_tag), repr(format))
|
||||||
return {handler}('{collection}', data, '{class_name}', {model_var_name}, format, add_submission_tag, api_key)
|
return {handler}('{collection}', data, '{class_name}', {model_var_name}, format, api_key, add_submission_tag=add_submission_tag)
|
||||||
|
"""
|
||||||
|
|
||||||
|
_endpoint_validate_template = """
|
||||||
|
def {name}(
|
||||||
|
data: {model_var_name}.{class_name} | Annotated[str, Body(media_type='text/plain')],
|
||||||
|
api_key: str = Depends(api_key_header_scheme),
|
||||||
|
format: Format = Format.json,
|
||||||
|
) -> JSONResponse | PlainTextResponse:
|
||||||
|
logger.info('{name}(%s, %s, %s, %s)', repr(data), repr('{class_name}'), repr({model_var_name}), repr(format))
|
||||||
|
return {handler}('{collection}', data, '{class_name}', {model_var_name}, format, api_key)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_endpoint_curated_template = """
|
_endpoint_curated_template = """
|
||||||
|
|
@ -305,7 +316,9 @@ def write_record_dir_config(
|
||||||
backend_config: RecordDirBackendConfig,
|
backend_config: RecordDirBackendConfig,
|
||||||
schema: str,
|
schema: str,
|
||||||
):
|
):
|
||||||
assert isinstance(backend_config, RecordDirBackendConfig)
|
if not isinstance(backend_config, RecordDirBackendConfig):
|
||||||
|
msg = 'backend_config is not an instance of RecordDirBackendConfig'
|
||||||
|
raise TypeError(msg)
|
||||||
|
|
||||||
record_dir_config_file_path = path / record_dir_config_file_name
|
record_dir_config_file_path = path / record_dir_config_file_name
|
||||||
if not record_dir_config_file_path.exists():
|
if not record_dir_config_file_path.exists():
|
||||||
|
|
@ -494,7 +507,7 @@ def create_endpoints_for_collection(
|
||||||
(
|
(
|
||||||
'validate',
|
'validate',
|
||||||
'validate/record',
|
'validate/record',
|
||||||
_endpoint_template,
|
_endpoint_validate_template,
|
||||||
'validate_record',
|
'validate_record',
|
||||||
'validate',
|
'validate',
|
||||||
f'Validate records for collection "{collection_name}"',
|
f'Validate records for collection "{collection_name}"',
|
||||||
|
|
@ -556,8 +569,6 @@ def delete_endpoint(
|
||||||
operation_path: str,
|
operation_path: str,
|
||||||
app: FastAPI,
|
app: FastAPI,
|
||||||
):
|
):
|
||||||
from fastapi.routing import _IncludedRouter
|
|
||||||
|
|
||||||
remove_paths_set = {
|
remove_paths_set = {
|
||||||
f'/{collection_name}/{operation_path}/{class_name}'
|
f'/{collection_name}/{operation_path}/{class_name}'
|
||||||
for class_name in active_classes
|
for class_name in active_classes
|
||||||
|
|
@ -579,8 +590,9 @@ def store_record(
|
||||||
class_name: str,
|
class_name: str,
|
||||||
model: Any,
|
model: Any,
|
||||||
input_format: Format,
|
input_format: Format,
|
||||||
add_submission_tag: bool,
|
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
api_key: str | None = Depends(api_key_header_scheme),
|
||||||
|
*,
|
||||||
|
add_submission_tag: bool,
|
||||||
) -> JSONResponse | PlainTextResponse:
|
) -> JSONResponse | PlainTextResponse:
|
||||||
if input_format == Format.json and isinstance(data, str):
|
if input_format == Format.json and isinstance(data, str):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -615,7 +627,6 @@ def store_record(
|
||||||
)
|
)
|
||||||
final_permissions = join_default_token_permissions(
|
final_permissions = join_default_token_permissions(
|
||||||
abstract_config,
|
abstract_config,
|
||||||
instance_state,
|
|
||||||
token_permissions,
|
token_permissions,
|
||||||
collection,
|
collection,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ async def replace_collection(
|
||||||
async def create_or_replace_collection(
|
async def create_or_replace_collection(
|
||||||
body: CollectionRequest,
|
body: CollectionRequest,
|
||||||
api_key: str,
|
api_key: str,
|
||||||
|
*,
|
||||||
allow_replace: bool,
|
allow_replace: bool,
|
||||||
):
|
):
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
|
|
@ -266,8 +267,9 @@ def validate_incoming_paths(
|
||||||
token_collection_info = token_info.collections.get(collection_request.name)
|
token_collection_info = token_info.collections.get(collection_request.name)
|
||||||
if token_collection_info:
|
if token_collection_info:
|
||||||
token_permissions = get_token_permissions(token_collection_info.mode)
|
token_permissions = get_token_permissions(token_collection_info.mode)
|
||||||
if token_permissions.incoming_write or token_permissions.zones_access:
|
if (
|
||||||
if not collection_request.incoming:
|
token_permissions.incoming_write or token_permissions.zones_access
|
||||||
|
) and not collection_request.incoming:
|
||||||
detail = (
|
detail = (
|
||||||
f"Cannot add collection '{collection_request.name}' without "
|
f"Cannot add collection '{collection_request.name}' without "
|
||||||
f"`incoming` path, because at least token '{token_name}' "
|
f"`incoming` path, because at least token '{token_name}' "
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,6 @@ from dump_things_service.abstract_config import (
|
||||||
get_config_labels,
|
get_config_labels,
|
||||||
read_config,
|
read_config,
|
||||||
)
|
)
|
||||||
from dump_things_service.backends.schema_type_layer import _SchemaTypeLayer
|
|
||||||
from dump_things_service.backends.sqlite import _SQLiteBackend
|
|
||||||
from dump_things_service.exceptions import CurieResolutionError
|
from dump_things_service.exceptions import CurieResolutionError
|
||||||
from dump_things_service.instance_state import create_instance_state
|
from dump_things_service.instance_state import create_instance_state
|
||||||
from dump_things_service.manifest import manifest_configuration
|
from dump_things_service.manifest import manifest_configuration
|
||||||
|
|
@ -40,7 +38,7 @@ parser.add_argument(
|
||||||
def check_pids_in_stores(stores: Iterable[_ModelStore]) -> int:
|
def check_pids_in_stores(stores: Iterable[_ModelStore]) -> int:
|
||||||
result = 0
|
result = 0
|
||||||
for store in stores:
|
for store in stores:
|
||||||
print('checking', store.get_uri(), file=sys.stderr)
|
print('checking', store.get_uri(), file=sys.stderr) # noqa T201 -- cli result output
|
||||||
for record_info in store.get_all_objects():
|
for record_info in store.get_all_objects():
|
||||||
pid = record_info.json_object['pid']
|
pid = record_info.json_object['pid']
|
||||||
try:
|
try:
|
||||||
|
|
@ -97,7 +95,7 @@ def main():
|
||||||
arguments = parser.parse_args()
|
arguments = parser.parse_args()
|
||||||
result = check_pids(Path(arguments.store).absolute())
|
result = check_pids(Path(arguments.store).absolute())
|
||||||
if result > 0:
|
if result > 0:
|
||||||
print(f'found {result} unresolvable pids', file=sys.stderr)
|
print(f'found {result} unresolvable pids', file=sys.stderr) # noqa T201 -- cli result output
|
||||||
return 1
|
return 1
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ def main():
|
||||||
allow_unicode=True,
|
allow_unicode=True,
|
||||||
sort_keys=False,
|
sort_keys=False,
|
||||||
)
|
)
|
||||||
print(text)
|
print(text) # noqa T201 -- cli result output
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from argparse import ArgumentParser
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
|
from starlette.status import HTTP_300_MULTIPLE_CHOICES
|
||||||
|
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog='Download a complete configuration of a running service',
|
prog='Download a complete configuration of a running service',
|
||||||
|
|
@ -59,7 +60,7 @@ def main():
|
||||||
|
|
||||||
admin_token = os.environ.get('DTS_ADMIN_TOKEN')
|
admin_token = os.environ.get('DTS_ADMIN_TOKEN')
|
||||||
if not admin_token:
|
if not admin_token:
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
'An admin token must be provided in the environment variable `DTS_ADMIN_TOKEN`',
|
'An admin token must be provided in the environment variable `DTS_ADMIN_TOKEN`',
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
flush=True,
|
flush=True,
|
||||||
|
|
@ -73,9 +74,9 @@ def main():
|
||||||
)
|
)
|
||||||
|
|
||||||
if arguments.format == 'json':
|
if arguments.format == 'json':
|
||||||
print(json.dumps(configuration, indent=2, sort_keys=False))
|
print(json.dumps(configuration, indent=2, sort_keys=False)) # noqa T201 -- cli result output
|
||||||
elif arguments.format == 'yaml':
|
elif arguments.format == 'yaml':
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
yaml.dump(
|
yaml.dump(
|
||||||
data=configuration,
|
data=configuration,
|
||||||
sort_keys=False,
|
sort_keys=False,
|
||||||
|
|
@ -169,8 +170,8 @@ def _get_data(
|
||||||
token: str,
|
token: str,
|
||||||
content_class: str,
|
content_class: str,
|
||||||
) -> list:
|
) -> list:
|
||||||
result = requests.get(url, headers={'x-dumpthings-token': token})
|
result = requests.get(url, headers={'x-dumpthings-token': token}, timeout=10)
|
||||||
if result.status_code >= 300:
|
if result.status_code >= HTTP_300_MULTIPLE_CHOICES:
|
||||||
msg = f'Error downloading {content_class} from {url}: {result.text}'
|
msg = f'Error downloading {content_class} from {url}: {result.text}'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
return result.json()
|
return result.json()
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ def main():
|
||||||
arguments = parser.parse_args()
|
arguments = parser.parse_args()
|
||||||
|
|
||||||
audit_backend = GitAuditBackend(Path(arguments.audit_store))
|
audit_backend = GitAuditBackend(Path(arguments.audit_store))
|
||||||
audit_backend._rebuild_index()
|
audit_backend.rebuild_index()
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
re.compile(arguments.pid)
|
re.compile(arguments.pid)
|
||||||
except re.error as e:
|
except re.error as e:
|
||||||
print('Error in PID pattern:', e, file=sys.stderr, flush=True)
|
print('Error in PID pattern:', e, file=sys.stderr, flush=True) # noqa T201 -- cli result output
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
audit_backend = GitAuditBackend(Path(arguments.audit_store))
|
audit_backend = GitAuditBackend(Path(arguments.audit_store))
|
||||||
|
|
@ -47,7 +47,7 @@ def main():
|
||||||
'diff': change[2],
|
'diff': change[2],
|
||||||
'resulting-record': change[3],
|
'resulting-record': change[3],
|
||||||
}
|
}
|
||||||
print(json.dumps(report, ensure_ascii=False), flush=True)
|
print(json.dumps(report, ensure_ascii=False), flush=True) # noqa T201 -- cli result output
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ def main():
|
||||||
|
|
||||||
token = arguments.token.strip()
|
token = arguments.token.strip()
|
||||||
if any(s.isspace() for s in token):
|
if any(s.isspace() for s in token):
|
||||||
print('Whitespace are not allowed in token', file=sys.stderr, flush=True)
|
print('Whitespace are not allowed in token', file=sys.stderr, flush=True) # noqa T201 -- cli result output
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(hash_token_representation(token))
|
print(hash_token_representation(token)) # noqa T201 -- cli result output
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,12 @@ from pathlib import Path
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import yaml
|
||||||
|
from starlette.status import HTTP_300_MULTIPLE_CHOICES
|
||||||
|
|
||||||
from dump_things_service.instance_state import get_record_dir_config
|
from dump_things_service.instance_state import get_record_dir_config
|
||||||
|
|
||||||
|
CONFIG_VERSION = 2
|
||||||
|
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
prog='Establish a configuration in a running service',
|
prog='Establish a configuration in a running service',
|
||||||
description='Read a configuration from a dump-things configuration-file '
|
description='Read a configuration from a dump-things configuration-file '
|
||||||
|
|
@ -76,7 +79,7 @@ def main():
|
||||||
elif file_type == 'yaml':
|
elif file_type == 'yaml':
|
||||||
configuration = yaml.safe_load(config_file)
|
configuration = yaml.safe_load(config_file)
|
||||||
else:
|
else:
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
f'Unknown file type {config_file_path} (use `.json` or `.yaml` suffix, or specify the format with --format <json|yaml>)',
|
f'Unknown file type {config_file_path} (use `.json` or `.yaml` suffix, or specify the format with --format <json|yaml>)',
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
flush=True,
|
flush=True,
|
||||||
|
|
@ -86,22 +89,25 @@ def main():
|
||||||
if arguments.old_format:
|
if arguments.old_format:
|
||||||
configuration = convert_config_1_to_config_2(configuration, arguments.store)
|
configuration = convert_config_1_to_config_2(configuration, arguments.store)
|
||||||
elif arguments.store:
|
elif arguments.store:
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
'Warning: ignoring `--store` option because `--old-format` '
|
'Warning: ignoring `--store` option because `--old-format` '
|
||||||
'is not provided.',
|
'is not provided.',
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
flush=True,
|
flush=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert configuration['type'] == 'collections', (
|
if configuration['type'] != 'collections':
|
||||||
'`type: collections` missing in config-file'
|
msg = '`type: collections` missing in config-file'
|
||||||
)
|
raise ValueError(msg)
|
||||||
assert configuration['version'] == 2, '`version: 2` missing in config-file'
|
|
||||||
|
if configuration['version'] != CONFIG_VERSION:
|
||||||
|
msg = f'`version: {CONFIG_VERSION}` missing in config-file'
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
if arguments.send_to:
|
if arguments.send_to:
|
||||||
admin_token = os.environ.get('DTS_ADMIN_TOKEN')
|
admin_token = os.environ.get('DTS_ADMIN_TOKEN')
|
||||||
if not admin_token:
|
if not admin_token:
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
'An admin token must be provided in the environment variable `DTS_ADMIN_TOKEN`',
|
'An admin token must be provided in the environment variable `DTS_ADMIN_TOKEN`',
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
flush=True,
|
flush=True,
|
||||||
|
|
@ -114,15 +120,16 @@ def main():
|
||||||
arguments.send_to.removesuffix('/'),
|
arguments.send_to.removesuffix('/'),
|
||||||
admin_token,
|
admin_token,
|
||||||
)
|
)
|
||||||
return 0
|
|
||||||
except RuntimeError as rte:
|
except RuntimeError as rte:
|
||||||
print(f'{rte.args[0]}', file=sys.stderr, flush=True)
|
print(f'{rte.args[0]}', file=sys.stderr, flush=True) # noqa T201 -- cli result output
|
||||||
return 2
|
return 2
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
if file_type == 'json':
|
if file_type == 'json':
|
||||||
print(json.dumps(configuration, indent=2, sort_keys=False))
|
print(json.dumps(configuration, indent=2, sort_keys=False)) # noqa T201 -- cli result output
|
||||||
elif file_type == 'yaml':
|
elif file_type == 'yaml':
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
yaml.dump(
|
yaml.dump(
|
||||||
data=configuration,
|
data=configuration,
|
||||||
sort_keys=False,
|
sort_keys=False,
|
||||||
|
|
@ -271,8 +278,9 @@ def _post_data(
|
||||||
url,
|
url,
|
||||||
headers={'x-dumpthings-token': token},
|
headers={'x-dumpthings-token': token},
|
||||||
json=data,
|
json=data,
|
||||||
|
timeout=20,
|
||||||
)
|
)
|
||||||
if result.status_code >= 300:
|
if result.status_code >= HTTP_300_MULTIPLE_CHOICES:
|
||||||
msg = f'Error uploading {content_class}: {content_name}: {result.text}'
|
msg = f'Error uploading {content_class}: {content_name}: {result.text}'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,8 +79,8 @@ add_pagination(router)
|
||||||
async def read_curated_records_of_type(
|
async def read_curated_records_of_type(
|
||||||
collection: str,
|
collection: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
if class_name not in instance_state.collections[collection].active_classes:
|
if class_name not in instance_state.collections[collection].active_classes:
|
||||||
|
|
@ -107,8 +107,8 @@ async def read_curated_records_of_type(
|
||||||
async def read_curated_records_of_type_paginated(
|
async def read_curated_records_of_type_paginated(
|
||||||
collection: str,
|
collection: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
) -> Page[dict]:
|
) -> Page[dict]:
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
if class_name not in instance_state.collections[collection].active_classes:
|
if class_name not in instance_state.collections[collection].active_classes:
|
||||||
|
|
@ -134,8 +134,8 @@ async def read_curated_records_of_type_paginated(
|
||||||
)
|
)
|
||||||
async def read_curated_all_records(
|
async def read_curated_all_records(
|
||||||
collection: str,
|
collection: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
return await _read_curated_records(
|
return await _read_curated_records(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
@ -154,8 +154,8 @@ async def read_curated_all_records(
|
||||||
)
|
)
|
||||||
async def read_curated_all_records_paginated(
|
async def read_curated_all_records_paginated(
|
||||||
collection: str,
|
collection: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
) -> Page[dict]:
|
) -> Page[dict]:
|
||||||
record_list = await _read_curated_records(
|
record_list = await _read_curated_records(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@ async def incoming_read_records_of_type(
|
||||||
collection: str,
|
collection: str,
|
||||||
label: str,
|
label: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
if class_name not in instance_state.collections[collection].active_classes:
|
if class_name not in instance_state.collections[collection].active_classes:
|
||||||
|
|
@ -111,8 +111,8 @@ async def incoming_read_records_of_type_paginated(
|
||||||
collection: str,
|
collection: str,
|
||||||
label: str,
|
label: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
) -> Page[dict]:
|
) -> Page[dict]:
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
if class_name not in instance_state.collections[collection].active_classes:
|
if class_name not in instance_state.collections[collection].active_classes:
|
||||||
|
|
@ -140,8 +140,8 @@ async def incoming_read_records_of_type_paginated(
|
||||||
async def incoming_read_all_records(
|
async def incoming_read_all_records(
|
||||||
collection: str,
|
collection: str,
|
||||||
label: str,
|
label: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
return await _incoming_read_records(
|
return await _incoming_read_records(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
@ -162,8 +162,8 @@ async def incoming_read_all_records(
|
||||||
async def incoming_read_all_records_paginated(
|
async def incoming_read_all_records_paginated(
|
||||||
collection: str,
|
collection: str,
|
||||||
label: str,
|
label: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
|
||||||
) -> Page[dict]:
|
) -> Page[dict]:
|
||||||
record_list = await _incoming_read_records(
|
record_list = await _incoming_read_records(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ def create_instance_state(
|
||||||
bootstrap_token: str,
|
bootstrap_token: str,
|
||||||
fastapi_app: FastAPI,
|
fastapi_app: FastAPI,
|
||||||
) -> InstanceState:
|
) -> InstanceState:
|
||||||
global g_instance_state
|
global g_instance_state # noqa PLW0603 -- this is cached on the first call
|
||||||
|
|
||||||
if g_instance_state:
|
if g_instance_state:
|
||||||
logger.warning('create_instance_state() already called')
|
logger.warning('create_instance_state() already called')
|
||||||
|
|
@ -123,8 +123,6 @@ def create_instance_state(
|
||||||
|
|
||||||
|
|
||||||
def get_instance_state() -> InstanceState:
|
def get_instance_state() -> InstanceState:
|
||||||
global g_instance_state
|
|
||||||
|
|
||||||
if not g_instance_state:
|
if not g_instance_state:
|
||||||
msg = 'get_instance_state() called before create_instance_state()'
|
msg = 'get_instance_state() called before create_instance_state()'
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from abc import (
|
||||||
ABCMeta,
|
ABCMeta,
|
||||||
abstractmethod,
|
abstractmethod,
|
||||||
)
|
)
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, SupportsIndex
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
|
|
@ -58,7 +58,7 @@ class LazyList(list, metaclass=ABCMeta):
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
return len(self.list_info)
|
return len(self.list_info)
|
||||||
|
|
||||||
def __getitem__(self, index: int) -> Any:
|
def __getitem__(self, index: SupportsIndex) -> Any:
|
||||||
if isinstance(index, slice):
|
if isinstance(index, slice):
|
||||||
start = 0 if index.start is None else index.start
|
start = 0 if index.start is None else index.start
|
||||||
stop = len(self) if index.stop is None else index.stop
|
stop = len(self) if index.stop is None else index.stop
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,8 @@ from dump_things_service.abstract_config import (
|
||||||
check_collection,
|
check_collection,
|
||||||
hash_token_representation,
|
hash_token_representation,
|
||||||
read_config,
|
read_config,
|
||||||
|
CONFIG_VERSION_1_ID,
|
||||||
|
CONFIG_VERSION_2_ID,
|
||||||
)
|
)
|
||||||
from dump_things_service.api_key import api_key_header_scheme
|
from dump_things_service.api_key import api_key_header_scheme
|
||||||
from dump_things_service.converter import (
|
from dump_things_service.converter import (
|
||||||
|
|
@ -184,7 +186,7 @@ if not arguments.admin_token_hash:
|
||||||
)
|
)
|
||||||
# Validate the hash token format
|
# Validate the hash token format
|
||||||
elif not hash_matcher.match(arguments.admin_token_hash):
|
elif not hash_matcher.match(arguments.admin_token_hash):
|
||||||
print(
|
print( # noqa T201 -- cli result output
|
||||||
'Hashed admin token is not a 64-digits hex-number',
|
'Hashed admin token is not a 64-digits hex-number',
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
flush=True,
|
flush=True,
|
||||||
|
|
@ -204,8 +206,8 @@ else:
|
||||||
|
|
||||||
store_path = Path(arguments.store).resolve()
|
store_path = Path(arguments.store).resolve()
|
||||||
if not store_path.exists():
|
if not store_path.exists():
|
||||||
logger.error(f'Store path does not exist: {store_path}')
|
logger.error('Store path does not exist: %s', str(store_path))
|
||||||
raise SystemExit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
disable_installed_extensions_check()
|
disable_installed_extensions_check()
|
||||||
|
|
@ -253,7 +255,7 @@ def initialize_from_config_file(
|
||||||
config_dict = yaml.safe_load(f)
|
config_dict = yaml.safe_load(f)
|
||||||
|
|
||||||
config_version = config_dict['version']
|
config_version = config_dict['version']
|
||||||
if config_version == 1:
|
if config_version == CONFIG_VERSION_1_ID:
|
||||||
logger.info(
|
logger.info(
|
||||||
'Converting version 1 configuration at %s',
|
'Converting version 1 configuration at %s',
|
||||||
arguments.config,
|
arguments.config,
|
||||||
|
|
@ -262,7 +264,7 @@ def initialize_from_config_file(
|
||||||
config_dict,
|
config_dict,
|
||||||
instance_state.store_path,
|
instance_state.store_path,
|
||||||
)
|
)
|
||||||
elif config_version != 2:
|
elif config_version != CONFIG_VERSION_2_ID:
|
||||||
msg = f'Invalid version in config file: {config_version}'
|
msg = f'Invalid version in config file: {config_version}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
|
@ -305,12 +307,15 @@ if not (
|
||||||
|
|
||||||
|
|
||||||
# If there are no structures in the configuration, check for a bootstrap token.
|
# If there are no structures in the configuration, check for a bootstrap token.
|
||||||
if not (
|
if (
|
||||||
|
not (
|
||||||
g_configuration.admin_tokens
|
g_configuration.admin_tokens
|
||||||
or g_configuration.collections
|
or g_configuration.collections
|
||||||
or g_configuration.tokens
|
or g_configuration.tokens
|
||||||
) and not g_instance_state.bootstrap_token:
|
)
|
||||||
print(
|
and not g_instance_state.bootstrap_token
|
||||||
|
):
|
||||||
|
print( # noqa T201 -- cli result output
|
||||||
'The server has an empty configuration and requires a bootstrap '
|
'The server has an empty configuration and requires a bootstrap '
|
||||||
'token (use `--admin-token-hash` to provide one)',
|
'token (use `--admin-token-hash` to provide one)',
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
|
|
@ -400,8 +405,8 @@ async def maintenance(
|
||||||
async def read_record_with_pid(
|
async def read_record_with_pid(
|
||||||
collection: str,
|
collection: str,
|
||||||
pid: str,
|
pid: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
format: Format = Format.json, # noqa: A002
|
format: Format = Format.json, # noqa: A002
|
||||||
api_key: str = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
check_collection(g_configuration, collection)
|
check_collection(g_configuration, collection)
|
||||||
|
|
||||||
|
|
@ -442,9 +447,9 @@ async def read_record_with_pid(
|
||||||
)
|
)
|
||||||
async def read_all_records(
|
async def read_all_records(
|
||||||
collection: str,
|
collection: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
format: Format = Format.json, # noqa: A002
|
format: Format = Format.json, # noqa: A002
|
||||||
api_key: str = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
return await _read_all_records(
|
return await _read_all_records(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
@ -465,9 +470,9 @@ async def read_all_records(
|
||||||
)
|
)
|
||||||
async def read_all_records_paginated(
|
async def read_all_records_paginated(
|
||||||
collection: str,
|
collection: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
format: Format = Format.json, # noqa: A002
|
format: Format = Format.json, # noqa: A002
|
||||||
api_key: str = Depends(api_key_header_scheme),
|
|
||||||
) -> Page[dict | str]:
|
) -> Page[dict | str]:
|
||||||
result_list = await _read_all_records(
|
result_list = await _read_all_records(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
@ -487,9 +492,9 @@ async def read_all_records_paginated(
|
||||||
async def read_records_of_type(
|
async def read_records_of_type(
|
||||||
collection: str,
|
collection: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
format: Format = Format.json, # noqa: A002
|
format: Format = Format.json, # noqa: A002
|
||||||
api_key: str = Depends(api_key_header_scheme),
|
|
||||||
):
|
):
|
||||||
return await _read_records_of_type(
|
return await _read_records_of_type(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
@ -512,9 +517,9 @@ async def read_records_of_type(
|
||||||
async def read_records_of_type_paginated(
|
async def read_records_of_type_paginated(
|
||||||
collection: str,
|
collection: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
|
api_key: Annotated[str, Depends(api_key_header_scheme)],
|
||||||
matching: str | None = None,
|
matching: str | None = None,
|
||||||
format: Format = Format.json, # noqa: A002
|
format: Format = Format.json, # noqa: A002
|
||||||
api_key: str = Depends(api_key_header_scheme),
|
|
||||||
) -> Page[dict | str]:
|
) -> Page[dict | str]:
|
||||||
result_list = await _read_records_of_type(
|
result_list = await _read_records_of_type(
|
||||||
collection=collection,
|
collection=collection,
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ def get_subclasses_2(
|
||||||
collection_name: str,
|
collection_name: str,
|
||||||
class_name: str,
|
class_name: str,
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
from dump_things_service.instance_state import get_instance_state
|
from dump_things_service.instance_state import get_instance_state # noqa PLC0415 -- global import leads to circular imports
|
||||||
|
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
schema_view = instance_state.schema_info[collection_name].schema_view
|
schema_view = instance_state.schema_info[collection_name].schema_view
|
||||||
|
|
@ -84,7 +84,7 @@ def compile_module_with_increasing_recursion_limit(
|
||||||
pydantic_generator: PydanticGenerator,
|
pydantic_generator: PydanticGenerator,
|
||||||
schema_location: str,
|
schema_location: str,
|
||||||
) -> ModuleType:
|
) -> ModuleType:
|
||||||
global current_recursion_limit
|
global current_recursion_limit # noqa PLW0603 -- global state is updated from within the call-tree
|
||||||
|
|
||||||
module = None
|
module = None
|
||||||
module_name = (
|
module_name = (
|
||||||
|
|
@ -107,8 +107,9 @@ def compile_module_with_increasing_recursion_limit(
|
||||||
sys.setrecursionlimit(current_recursion_limit)
|
sys.setrecursionlimit(current_recursion_limit)
|
||||||
lgr.warning(
|
lgr.warning(
|
||||||
'RecursionError when building Pydantic model for schema '
|
'RecursionError when building Pydantic model for schema '
|
||||||
f'{schema_location}, increasing recursion limit to: '
|
'%s, increasing recursion limit to: %d',
|
||||||
f'{current_recursion_limit}.'
|
schema_location,
|
||||||
|
current_recursion_limit,
|
||||||
)
|
)
|
||||||
return module
|
return module
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import (
|
||||||
|
UTC,
|
||||||
|
datetime,
|
||||||
|
)
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
|
@ -100,7 +103,7 @@ class _ModelStore:
|
||||||
submitter_curie_or_iri = self.get_curie(self.tags['id'])
|
submitter_curie_or_iri = self.get_curie(self.tags['id'])
|
||||||
time_curie_or_iri = self.get_curie(self.tags['time'])
|
time_curie_or_iri = self.get_curie(self.tags['time'])
|
||||||
json_object['annotations'][submitter_curie_or_iri] = submitter
|
json_object['annotations'][submitter_curie_or_iri] = submitter
|
||||||
json_object['annotations'][time_curie_or_iri] = datetime.now().isoformat()
|
json_object['annotations'][time_curie_or_iri] = datetime.now(tz=UTC).isoformat()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def homogenize_annotations(json_object) -> dict:
|
def homogenize_annotations(json_object) -> dict:
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ from types import ModuleType
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import yaml
|
import yaml
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from dump_things_service.abstract_config import (
|
from dump_things_service.abstract_config import (
|
||||||
GitAuditBackendConfig,
|
GitAuditBackendConfig,
|
||||||
|
|
@ -366,7 +367,7 @@ def fastapi_app_simple(dump_stores_simple):
|
||||||
'--ignore-default-config-file',
|
'--ignore-default-config-file',
|
||||||
str(tmp_path),
|
str(tmp_path),
|
||||||
]
|
]
|
||||||
from dump_things_service.main import app
|
from dump_things_service.main import app # noqa PLC0415 -- main contains one-time-code that is not guarded
|
||||||
|
|
||||||
sys.argv = old_sys_argv
|
sys.argv = old_sys_argv
|
||||||
return app, tmp_path, audit_tmp_path, admin_token
|
return app, tmp_path, audit_tmp_path, admin_token
|
||||||
|
|
@ -374,8 +375,6 @@ def fastapi_app_simple(dump_stores_simple):
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def fastapi_client_simple(fastapi_app_simple):
|
def fastapi_client_simple(fastapi_app_simple):
|
||||||
from fastapi.testclient import TestClient
|
|
||||||
|
|
||||||
test_client = TestClient(fastapi_app_simple[0])
|
test_client = TestClient(fastapi_app_simple[0])
|
||||||
store_path = fastapi_app_simple[1]
|
store_path = fastapi_app_simple[1]
|
||||||
audit_path = fastapi_app_simple[2]
|
audit_path = fastapi_app_simple[2]
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ def test_search_by_class(fastapi_client_simple):
|
||||||
)
|
)
|
||||||
assert response.status_code == HTTP_200_OK
|
assert response.status_code == HTTP_200_OK
|
||||||
json_result = response.json()
|
json_result = response.json()
|
||||||
if len(json_result) == 3: # noqa: PLR2004
|
if len(json_result) == 3:
|
||||||
assert response.json() == [
|
assert response.json() == [
|
||||||
{
|
{
|
||||||
'given_name': 'curated',
|
'given_name': 'curated',
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ def test_read_curated_records(
|
||||||
else:
|
else:
|
||||||
assert len(json_object) == 3
|
assert len(json_object) == 3
|
||||||
|
|
||||||
for pattern, count in (('%25wolf%25', 1), ('%25cura%25', 2)):
|
for pattern, occurrences in (('%25wolf%25', 1), ('%25cura%25', 2)):
|
||||||
test_client, _, _ = fastapi_client_simple
|
test_client, _, _ = fastapi_client_simple
|
||||||
response = test_client.get(
|
response = test_client.get(
|
||||||
f'/collection_8/curated/records/{paginate}{class_name}?matching={pattern}',
|
f'/collection_8/curated/records/{paginate}{class_name}?matching={pattern}',
|
||||||
|
|
@ -49,9 +49,9 @@ def test_read_curated_records(
|
||||||
assert response.status_code == HTTP_200_OK
|
assert response.status_code == HTTP_200_OK
|
||||||
json_object = response.json()
|
json_object = response.json()
|
||||||
if 'items' in json_object:
|
if 'items' in json_object:
|
||||||
assert len(json_object['items']) == count
|
assert len(json_object['items']) == occurrences
|
||||||
else:
|
else:
|
||||||
assert len(json_object) == count
|
assert len(json_object) == occurrences
|
||||||
|
|
||||||
|
|
||||||
def test_read_curated_records_by_pid(fastapi_client_simple):
|
def test_read_curated_records_by_pid(fastapi_client_simple):
|
||||||
|
|
@ -179,8 +179,7 @@ def test_audit_backend_auto_flush(fastapi_client_simple):
|
||||||
for i in count():
|
for i in count():
|
||||||
if not audit_backend.current_change_set:
|
if not audit_backend.current_change_set:
|
||||||
break
|
break
|
||||||
i += 1
|
if (i + 1) == 10:
|
||||||
if i == 10:
|
|
||||||
msg = 'auto flush did not trigger within 10 seconds'
|
msg = 'auto flush did not trigger within 10 seconds'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ zones_filled = False
|
||||||
|
|
||||||
|
|
||||||
def fill_zones(test_client):
|
def fill_zones(test_client):
|
||||||
global zones_filled
|
global zones_filled # noqa PLW0603 -- records global state from within test harness calls
|
||||||
|
|
||||||
if zones_filled:
|
if zones_filled:
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
|
import secrets
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
|
@ -58,7 +58,7 @@ class AdminTokenRequest(AdminTokenConfig):
|
||||||
|
|
||||||
def get_token_parts(token: str) -> list[str]:
|
def get_token_parts(token: str) -> list[str]:
|
||||||
parts = token.split('-', 1)
|
parts = token.split('-', 1)
|
||||||
if len(parts) != 2:
|
if len(parts) != 2: # noqa PLR2004 -- explicit check for two parts
|
||||||
msg = 'Invalid token format'
|
msg = 'Invalid token format'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
return parts
|
return parts
|
||||||
|
|
@ -156,7 +156,7 @@ def create_or_replace_token(
|
||||||
# Generate a random representation that does not yet exist.
|
# Generate a random representation that does not yet exist.
|
||||||
collision = True
|
collision = True
|
||||||
while collision:
|
while collision:
|
||||||
body.representation = random.randbytes(24).hex()
|
body.representation = secrets.token_bytes(24).hex()
|
||||||
existing_token_info = get_token_info_by_representation(
|
existing_token_info = get_token_info_by_representation(
|
||||||
abstract_config=abstract_config,
|
abstract_config=abstract_config,
|
||||||
token_representation=body.representation,
|
token_representation=body.representation,
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,6 @@ def wrap_http_exception(
|
||||||
|
|
||||||
def join_default_token_permissions(
|
def join_default_token_permissions(
|
||||||
abstract_configuration: Configuration,
|
abstract_configuration: Configuration,
|
||||||
instance_state: InstanceState,
|
|
||||||
permissions: TokenPermission,
|
permissions: TokenPermission,
|
||||||
collection: str,
|
collection: str,
|
||||||
) -> TokenPermission:
|
) -> TokenPermission:
|
||||||
|
|
@ -319,10 +318,10 @@ def create_token_store(
|
||||||
collection_name: str,
|
collection_name: str,
|
||||||
store_dir: Path,
|
store_dir: Path,
|
||||||
) -> _ModelStore:
|
) -> _ModelStore:
|
||||||
from dump_things_service.abstract_config import get_backend_and_extension
|
from dump_things_service.abstract_config import get_backend_and_extension # noqa PLC0415 -- global import leads to circular imports
|
||||||
from dump_things_service.backends.schema_type_layer import SchemaTypeLayer
|
from dump_things_service.backends.schema_type_layer import SchemaTypeLayer # noqa PLC0415 -- global import leads to circular imports
|
||||||
from dump_things_service.exceptions import ConfigError
|
from dump_things_service.exceptions import ConfigError # noqa PLC0415 -- global import leads to circular imports
|
||||||
from dump_things_service.store.model_store import ModelStore
|
from dump_things_service.store.model_store import ModelStore # noqa PLC0415 -- global import leads to circular imports
|
||||||
|
|
||||||
# One early requirement for the service was to be able to specify
|
# One early requirement for the service was to be able to specify
|
||||||
# arbitrary directories for curated stores and incoming stores. This
|
# arbitrary directories for curated stores and incoming stores. This
|
||||||
|
|
@ -391,8 +390,8 @@ def create_record_dir_token_store_backend(
|
||||||
mapping_function: str,
|
mapping_function: str,
|
||||||
suffix: str,
|
suffix: str,
|
||||||
) -> _RecordDirStore:
|
) -> _RecordDirStore:
|
||||||
from dump_things_service.backends.record_dir import RecordDirStore
|
from dump_things_service.backends.record_dir import RecordDirStore # noqa PLC0415 -- global import leads to circular imports
|
||||||
from dump_things_service.instance_state import record_dir_config_file_name
|
from dump_things_service.instance_state import record_dir_config_file_name # noqa PLC0415 -- global import leads to circular imports
|
||||||
|
|
||||||
# Write the configuration to the store, if it does not yet exist.
|
# Write the configuration to the store, if it does not yet exist.
|
||||||
if not (store_dir / record_dir_config_file_name).exists():
|
if not (store_dir / record_dir_config_file_name).exists():
|
||||||
|
|
@ -417,7 +416,7 @@ def write_record_dir_config(
|
||||||
mapping_function: str,
|
mapping_function: str,
|
||||||
schema: str,
|
schema: str,
|
||||||
):
|
):
|
||||||
from dump_things_service.instance_state import record_dir_config_file_name
|
from dump_things_service.instance_state import record_dir_config_file_name # noqa PLC0415 -- global import leads to circular imports
|
||||||
|
|
||||||
record_dir_config_file_path = path / record_dir_config_file_name
|
record_dir_config_file_path = path / record_dir_config_file_name
|
||||||
if not record_dir_config_file_path.exists():
|
if not record_dir_config_file_path.exists():
|
||||||
|
|
@ -436,8 +435,8 @@ def create_sqlite_token_store_backend(
|
||||||
store_dir: Path,
|
store_dir: Path,
|
||||||
order_by: list[str],
|
order_by: list[str],
|
||||||
) -> _SQLiteBackend:
|
) -> _SQLiteBackend:
|
||||||
from dump_things_service.backends.sqlite import SQLiteBackend
|
from dump_things_service.backends.sqlite import SQLiteBackend # noqa PLC0415 -- global import leads to circular imports
|
||||||
from dump_things_service.backends.sqlite import (
|
from dump_things_service.backends.sqlite import ( # noqa PLC0415 -- global import leads to circular imports
|
||||||
record_file_name as sqlite_record_file_name,
|
record_file_name as sqlite_record_file_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -466,7 +465,7 @@ async def process_token(
|
||||||
) -> tuple[TokenPermission, _ModelStore]:
|
) -> tuple[TokenPermission, _ModelStore]:
|
||||||
if api_key is None:
|
if api_key is None:
|
||||||
collection_config = get_collection_config_by_name(abstract_config, collection)
|
collection_config = get_collection_config_by_name(abstract_config, collection)
|
||||||
token_store, token_permissions, user_id = get_token_store(
|
token_store, token_permissions, _user_id = get_token_store(
|
||||||
abstract_config,
|
abstract_config,
|
||||||
instance_state,
|
instance_state,
|
||||||
collection,
|
collection,
|
||||||
|
|
@ -482,7 +481,7 @@ async def process_token(
|
||||||
)
|
)
|
||||||
|
|
||||||
final_permissions = join_default_token_permissions(
|
final_permissions = join_default_token_permissions(
|
||||||
abstract_config, instance_state, token_permissions, collection
|
abstract_config, token_permissions, collection
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check for maintenance mode
|
# Check for maintenance mode
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ def validate_record(
|
||||||
class_name: str,
|
class_name: str,
|
||||||
model: Any,
|
model: Any,
|
||||||
input_format: Format,
|
input_format: Format,
|
||||||
_: bool,
|
|
||||||
api_key: str | None = Depends(api_key_header_scheme),
|
api_key: str | None = Depends(api_key_header_scheme),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
instance_state = get_instance_state()
|
instance_state = get_instance_state()
|
||||||
|
|
@ -70,7 +69,6 @@ def validate_record(
|
||||||
)
|
)
|
||||||
final_permissions = join_default_token_permissions(
|
final_permissions = join_default_token_permissions(
|
||||||
abstract_config,
|
abstract_config,
|
||||||
instance_state,
|
|
||||||
token_permissions,
|
token_permissions,
|
||||||
collection,
|
collection,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -170,14 +170,27 @@ quote-style = "single"
|
||||||
"**/test_*" = [
|
"**/test_*" = [
|
||||||
# permit assert statements in tests
|
# permit assert statements in tests
|
||||||
"S101",
|
"S101",
|
||||||
|
# permit hard-coded passwords in tests
|
||||||
|
"S105", "S106",
|
||||||
|
# permit insecure hash in tests
|
||||||
|
"S324",
|
||||||
|
# permit standard random number generators in tests
|
||||||
|
"S311",
|
||||||
|
# permit magic values in tests
|
||||||
|
"PLR2004",
|
||||||
# permit relative import in tests
|
# permit relative import in tests
|
||||||
"TID252",
|
"TID252",
|
||||||
# permit versatile function names in tests
|
# permit versatile function names in tests
|
||||||
"N802",
|
"N802",
|
||||||
|
# permit access to private members in tests
|
||||||
|
"SLF001",
|
||||||
]
|
]
|
||||||
|
# permit hard-coded passwords, assert, insecure hash and magic values in fixtures
|
||||||
|
"dump_things_service/tests/fixtures.py" = ["S101", "S105", "S106", "S324", "PLR2004"]
|
||||||
# permit relative import in subpackage root
|
# permit relative import in subpackage root
|
||||||
"dump_things_service/*/__init__.py" = ["TID252"]
|
"dump_things_service/*/__init__.py" = ["TID252"]
|
||||||
|
|
||||||
|
|
||||||
[tool.codespell]
|
[tool.codespell]
|
||||||
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
|
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
|
||||||
skip = '.git*'
|
skip = '.git*'
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue