Do not create automated submission-tags by default #240
10 changed files with 80 additions and 31 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
|
@ -1,20 +1,30 @@
|
||||||
|
# 6.3.0 (2026-06-29)
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
- Automated annotation adding in write-record-endpoints has been removed.
|
||||||
|
The new query parameter `add_submission_tag` is added to write-record-endpoints.
|
||||||
|
If set to True, the server will add an automated annotation with the annotation
|
||||||
|
tags that are defined in the server-configuration (the default is False).
|
||||||
|
|
||||||
|
|
||||||
# 6.2.2 (2026-06-26)
|
# 6.2.2 (2026-06-26)
|
||||||
|
|
||||||
# Bugfixes
|
## Bugfixes
|
||||||
|
|
||||||
- Fix a bug in authentication when non-config tokens are used.
|
- Fix a bug in authentication when non-config tokens are used.
|
||||||
|
|
||||||
|
|
||||||
# 6.2.1 (2026-06-26)
|
# 6.2.1 (2026-06-26)
|
||||||
|
|
||||||
# Bugfixes
|
## Bugfixes
|
||||||
|
|
||||||
- Fix a bug in curator-writing when non-config tokens are used.
|
- Fix a bug in curator-writing when non-config tokens are used.
|
||||||
|
|
||||||
|
|
||||||
# 6.2.0 (2026-06-24)
|
# 6.2.0 (2026-06-24)
|
||||||
|
|
||||||
# New features
|
## New features
|
||||||
|
|
||||||
- Add PUT-methods to the administration endpoints `/tokens`, `/collections` and
|
- Add PUT-methods to the administration endpoints `/tokens`, `/collections` and
|
||||||
`/admin_tokens`. This allows to update collections, tokens, and
|
`/admin_tokens`. This allows to update collections, tokens, and
|
||||||
|
|
@ -31,7 +41,7 @@
|
||||||
|
|
||||||
# 6.1.0 (2026-06-22)
|
# 6.1.0 (2026-06-22)
|
||||||
|
|
||||||
# New features
|
## New features
|
||||||
|
|
||||||
- Add the option: `--ignore-default-config-file`. If the option is provided
|
- Add the option: `--ignore-default-config-file`. If the option is provided
|
||||||
a version 6 server will not try to initialize its configuration from the
|
a version 6 server will not try to initialize its configuration from the
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
__version__ = '6.2.2'
|
__version__ = '6.3.0'
|
||||||
|
|
|
||||||
|
|
@ -84,10 +84,11 @@ _endpoint_template = """
|
||||||
def {name}(
|
def {name}(
|
||||||
data: {model_var_name}.{class_name} | Annotated[str, Body(media_type='text/plain')],
|
data: {model_var_name}.{class_name} | Annotated[str, Body(media_type='text/plain')],
|
||||||
api_key: str = Depends(api_key_header_scheme),
|
api_key: str = Depends(api_key_header_scheme),
|
||||||
|
add_submission_tag: bool = False,
|
||||||
format: Format = Format.json,
|
format: Format = Format.json,
|
||||||
) -> JSONResponse | PlainTextResponse:
|
) -> JSONResponse | PlainTextResponse:
|
||||||
logger.info('{name}(%s, %s, %s, %s)', repr(data), repr('{class_name}'), repr({model_var_name}), 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, api_key)
|
return {handler}('{collection}', data, '{class_name}', {model_var_name}, format, add_submission_tag, api_key)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_endpoint_curated_template = """
|
_endpoint_curated_template = """
|
||||||
|
|
@ -536,6 +537,7 @@ 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),
|
||||||
) -> JSONResponse | PlainTextResponse:
|
) -> JSONResponse | PlainTextResponse:
|
||||||
if input_format == Format.json and isinstance(data, str):
|
if input_format == Format.json and isinstance(data, str):
|
||||||
|
|
@ -597,7 +599,10 @@ def store_record(
|
||||||
instance_state.validators[collection].validate(record)
|
instance_state.validators[collection].validate(record)
|
||||||
|
|
||||||
with wrap_http_exception(CurieResolutionError):
|
with wrap_http_exception(CurieResolutionError):
|
||||||
stored_records = store.store_object(obj=record, submitter=user_id)
|
stored_records = store.store_object(
|
||||||
|
obj=record,
|
||||||
|
submitter=user_id if add_submission_tag else None,
|
||||||
|
)
|
||||||
|
|
||||||
if input_format == Format.ttl:
|
if input_format == Format.ttl:
|
||||||
format_converter = FormatConverter(
|
format_converter = FormatConverter(
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ from dump_things_service import (
|
||||||
from dump_things_service.abstract_config import (
|
from dump_things_service.abstract_config import (
|
||||||
Configuration,
|
Configuration,
|
||||||
CollectionConfig,
|
CollectionConfig,
|
||||||
StrictModel,
|
|
||||||
store_config,
|
store_config,
|
||||||
get_config, get_token_permissions,
|
get_config, get_token_permissions,
|
||||||
)
|
)
|
||||||
|
|
@ -59,9 +58,6 @@ class TagSpec(BaseModel):
|
||||||
submission_time_tag: str = 'http://semanticscience.org/resource/SIO_001083'
|
submission_time_tag: str = 'http://semanticscience.org/resource/SIO_001083'
|
||||||
|
|
||||||
|
|
||||||
from pydantic import ConfigDict, Field
|
|
||||||
from dump_things_service.abstract_config import RecordDirBackendConfig, SQLiteBackendConfig, GitAuditBackendConfig
|
|
||||||
|
|
||||||
class CollectionRequest(CollectionConfig):
|
class CollectionRequest(CollectionConfig):
|
||||||
name: str
|
name: str
|
||||||
|
|
||||||
|
|
@ -124,7 +120,7 @@ async def create_or_replace_collection(
|
||||||
|
|
||||||
# Check for distinct directories.
|
# Check for distinct directories.
|
||||||
# TODO: we skip this currently because a number of version 5 installations
|
# TODO: we skip this currently because a number of version 5 installations
|
||||||
# deliberately put inboxes into the same path. Those configuration cannot
|
# deliberately put inboxes into the same path. Those configurations cannot
|
||||||
# be established if this check is performed. Instead of the `if False:`-
|
# be established if this check is performed. Instead of the `if False:`-
|
||||||
# clause, we should introduce a configuration for the server to specify
|
# clause, we should introduce a configuration for the server to specify
|
||||||
# whether unique directories are required.
|
# whether unique directories are required.
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ class _ModelStore:
|
||||||
def store_object(
|
def store_object(
|
||||||
self,
|
self,
|
||||||
obj: BaseModel,
|
obj: BaseModel,
|
||||||
submitter: str,
|
submitter: str | None,
|
||||||
) -> Iterable[tuple[str, dict]]:
|
) -> Iterable[tuple[str, dict]]:
|
||||||
if obj.__class__.__name__ == 'Thing' and 'dlthings:placeholder' in (obj.annotations or dict()):
|
if obj.__class__.__name__ == 'Thing' and 'dlthings:placeholder' in (obj.annotations or dict()):
|
||||||
return []
|
return []
|
||||||
|
|
@ -72,7 +72,7 @@ class _ModelStore:
|
||||||
def _store_flat_object(
|
def _store_flat_object(
|
||||||
self,
|
self,
|
||||||
obj: BaseModel,
|
obj: BaseModel,
|
||||||
submitter: str,
|
submitter: str | None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
iri = self.pid_to_iri(obj.pid)
|
iri = self.pid_to_iri(obj.pid)
|
||||||
class_name = obj.__class__.__name__
|
class_name = obj.__class__.__name__
|
||||||
|
|
@ -83,7 +83,9 @@ class _ModelStore:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add the submitter id to the record annotations
|
# Add the submitter id to the record annotations
|
||||||
self.annotate(json_object, submitter)
|
if submitter:
|
||||||
|
self.annotate(json_object, submitter)
|
||||||
|
|
||||||
self.backend.add_record(
|
self.backend.add_record(
|
||||||
iri=iri,
|
iri=iri,
|
||||||
class_name=class_name,
|
class_name=class_name,
|
||||||
|
|
|
||||||
|
|
@ -202,9 +202,7 @@ def test_store_record(fastapi_client_simple):
|
||||||
headers={'x-dumpthings-token': token},
|
headers={'x-dumpthings-token': token},
|
||||||
)
|
)
|
||||||
assert response.status_code == HTTP_200_OK
|
assert response.status_code == HTTP_200_OK
|
||||||
assert (
|
assert response.json() == extra_record
|
||||||
cleaned_json(response.json(), remove_keys=('annotations',)) == extra_record
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check that other collections do not report the new record
|
# Check that other collections do not report the new record
|
||||||
for i in range(3, 6):
|
for i in range(3, 6):
|
||||||
|
|
@ -223,13 +221,12 @@ def test_store_record(fastapi_client_simple):
|
||||||
f'/collection_{i}/records/Thing',
|
f'/collection_{i}/records/Thing',
|
||||||
headers={'x-dumpthings-token': token},
|
headers={'x-dumpthings-token': token},
|
||||||
)
|
)
|
||||||
cleaned_response = cleaned_json(response.json(), remove_keys=('annotations',))
|
assert extra_record in response.json()
|
||||||
assert extra_record in cleaned_response
|
|
||||||
assert {
|
assert {
|
||||||
'schema_type': 'abc:Person',
|
'schema_type': 'abc:Person',
|
||||||
'pid': pid,
|
'pid': pid,
|
||||||
'given_name': given_name,
|
'given_name': given_name,
|
||||||
} in cleaned_response
|
} in response.json()
|
||||||
|
|
||||||
# Check pagination
|
# Check pagination
|
||||||
for i, token in basic_write_locations:
|
for i, token in basic_write_locations:
|
||||||
|
|
@ -240,14 +237,12 @@ def test_store_record(fastapi_client_simple):
|
||||||
assert response.status_code == HTTP_200_OK
|
assert response.status_code == HTTP_200_OK
|
||||||
for key in ('items', 'total', 'page', 'size', 'pages'):
|
for key in ('items', 'total', 'page', 'size', 'pages'):
|
||||||
assert key in response.json()
|
assert key in response.json()
|
||||||
records = response.json()['items']
|
assert extra_record in response.json()['items']
|
||||||
cleaned_response = cleaned_json(records, remove_keys=('annotations',))
|
|
||||||
assert extra_record in cleaned_response
|
|
||||||
assert {
|
assert {
|
||||||
'schema_type': 'abc:Person',
|
'schema_type': 'abc:Person',
|
||||||
'pid': pid,
|
'pid': pid,
|
||||||
'given_name': given_name,
|
'given_name': given_name,
|
||||||
} in cleaned_response
|
} in response.json()['items']
|
||||||
|
|
||||||
|
|
||||||
def test_encoding(fastapi_client_simple):
|
def test_encoding(fastapi_client_simple):
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ def test_collection_adding(fastapi_client_simple):
|
||||||
headers={'x-dumpthings-token': new_token_representation},
|
headers={'x-dumpthings-token': new_token_representation},
|
||||||
)
|
)
|
||||||
assert response.status_code == HTTP_200_OK
|
assert response.status_code == HTTP_200_OK
|
||||||
assert cleaned_json(response.json()[0], ('annotations',)) == new_record
|
assert response.json()[0] == new_record
|
||||||
|
|
||||||
# Remove the token
|
# Remove the token
|
||||||
response = test_client.delete(
|
response = test_client.delete(
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,30 @@ ttl_result_record_a = """@prefix abc: <http://example.org/person-schema/abc/> .
|
||||||
@prefix oxo: <http://purl.obolibrary.org/obo/> .
|
@prefix oxo: <http://purl.obolibrary.org/obo/> .
|
||||||
@prefix xyz: <http://example.org/person-schema/xyz/> .
|
@prefix xyz: <http://example.org/person-schema/xyz/> .
|
||||||
|
|
||||||
|
xyz:HenryAdams a abc:Person ;
|
||||||
|
abc:annotations [ a abc:Annotation ;
|
||||||
|
abc:annotation_tag <http://semanticscience.org/resource/SIO_001083> ;
|
||||||
|
abc:annotation_value "1970-01-01T00:00:00" ] ;
|
||||||
|
abc:given_name "Henryöäß" ;
|
||||||
|
abc:schema_type "abc:Person" .
|
||||||
|
"""
|
||||||
|
|
||||||
|
ttl_result_record_b = """@prefix abc: <http://example.org/person-schema/abc/> .
|
||||||
|
@prefix oxo: <http://purl.obolibrary.org/obo/> .
|
||||||
|
@prefix xyz: <http://example.org/person-schema/xyz/> .
|
||||||
|
|
||||||
|
xyz:HenryAdams a abc:Person ;
|
||||||
|
abc:annotations [ a abc:Annotation ;
|
||||||
|
abc:annotation_tag oxo:NCIT_C54269 ;
|
||||||
|
abc:annotation_value "test_user_1" ] ;
|
||||||
|
abc:given_name "Henryöäß" ;
|
||||||
|
abc:schema_type "abc:Person" .
|
||||||
|
"""
|
||||||
|
|
||||||
|
ttl_result_record_with_annotations_a = """@prefix abc: <http://example.org/person-schema/abc/> .
|
||||||
|
@prefix oxo: <http://purl.obolibrary.org/obo/> .
|
||||||
|
@prefix xyz: <http://example.org/person-schema/xyz/> .
|
||||||
|
|
||||||
xyz:HenryAdams a abc:Person ;
|
xyz:HenryAdams a abc:Person ;
|
||||||
abc:annotations [ a abc:Annotation ;
|
abc:annotations [ a abc:Annotation ;
|
||||||
abc:annotation_tag <http://semanticscience.org/resource/SIO_001083> ;
|
abc:annotation_tag <http://semanticscience.org/resource/SIO_001083> ;
|
||||||
|
|
@ -35,7 +59,7 @@ xyz:HenryAdams a abc:Person ;
|
||||||
abc:schema_type "abc:Person" .
|
abc:schema_type "abc:Person" .
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ttl_result_record_b = """@prefix abc: <http://example.org/person-schema/abc/> .
|
ttl_result_record_with_annotations_b = """@prefix abc: <http://example.org/person-schema/abc/> .
|
||||||
@prefix oxo: <http://purl.obolibrary.org/obo/> .
|
@prefix oxo: <http://purl.obolibrary.org/obo/> .
|
||||||
@prefix xyz: <http://example.org/person-schema/xyz/> .
|
@prefix xyz: <http://example.org/person-schema/xyz/> .
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,23 @@ dlflatsocial:test_john_ttl a dlflatsocial:Person ;
|
||||||
dlsocialmx:given_name "Johnöüß" .
|
dlsocialmx:given_name "Johnöüß" .
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ttl_output_record_a = """@prefix dlflat: <https://concepts.datalad.org/s/flat/unreleased/> .
|
ttl_output_record_a = """@prefix dlflatsocial: <https://concepts.datalad.org/s/flat-social/unreleased/> .
|
||||||
|
@prefix dlsocialmx: <https://concepts.datalad.org/s/social-mixin/unreleased/> .
|
||||||
|
|
||||||
|
dlflatsocial:another_john_ttl a dlflatsocial:Person ;
|
||||||
|
dlsocialmx:given_name "Johnöüß" .
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ttl_output_record_b = """@prefix dlflatsocial: <https://concepts.datalad.org/s/flat-social/unreleased/> .
|
||||||
|
@prefix dlsocialmx: <https://concepts.datalad.org/s/social-mixin/unreleased/> .
|
||||||
|
|
||||||
|
dlflatsocial:another_john_ttl a dlflatsocial:Person ;
|
||||||
|
dlsocialmx:given_name "Johnöüß" .
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
ttl_output_record_with_annotation_a = """@prefix dlflat: <https://concepts.datalad.org/s/flat/unreleased/> .
|
||||||
@prefix dlflatsocial: <https://concepts.datalad.org/s/flat-social/unreleased/> .
|
@prefix dlflatsocial: <https://concepts.datalad.org/s/flat-social/unreleased/> .
|
||||||
@prefix dlsocialmx: <https://concepts.datalad.org/s/social-mixin/unreleased/> .
|
@prefix dlsocialmx: <https://concepts.datalad.org/s/social-mixin/unreleased/> .
|
||||||
@prefix dlthings: <https://concepts.datalad.org/s/things/v1/> .
|
@prefix dlthings: <https://concepts.datalad.org/s/things/v1/> .
|
||||||
|
|
@ -52,7 +68,7 @@ dlflatsocial:another_john_ttl a dlflatsocial:Person ;
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
ttl_output_record_b = """@prefix dlflat: <https://concepts.datalad.org/s/flat/unreleased/> .
|
ttl_output_record_with_annotation_b = """@prefix dlflat: <https://concepts.datalad.org/s/flat/unreleased/> .
|
||||||
@prefix dlflatsocial: <https://concepts.datalad.org/s/flat-social/unreleased/> .
|
@prefix dlflatsocial: <https://concepts.datalad.org/s/flat-social/unreleased/> .
|
||||||
@prefix dlsocialmx: <https://concepts.datalad.org/s/social-mixin/unreleased/> .
|
@prefix dlsocialmx: <https://concepts.datalad.org/s/social-mixin/unreleased/> .
|
||||||
@prefix dlthings: <https://concepts.datalad.org/s/things/v1/> .
|
@prefix dlthings: <https://concepts.datalad.org/s/things/v1/> .
|
||||||
|
|
@ -108,7 +124,7 @@ def test_json_ttl_json_dlflatsocial(fastapi_client_simple):
|
||||||
headers={'x-dumpthings-token': 'token-all'},
|
headers={'x-dumpthings-token': 'token-all'},
|
||||||
)
|
)
|
||||||
assert response.status_code == HTTP_200_OK
|
assert response.status_code == HTTP_200_OK
|
||||||
json_object = cleaned_json(response.json(), remove_keys=('annotations',))
|
json_object = response.json()
|
||||||
assert cleaned_json(json_object, remove_keys=('schema_type',)) != json_record
|
assert cleaned_json(json_object, remove_keys=('schema_type',)) != json_record
|
||||||
json_object['pid'] = json_record['pid']
|
json_object['pid'] = json_record['pid']
|
||||||
assert json_object == json_record_out
|
assert json_object == json_record_out
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ 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:
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue