rucio 37.7.0__py3-none-any.whl → 38.0.0rc1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rucio might be problematic. Click here for more details.
- rucio/alembicrevision.py +1 -1
- rucio/cli/bin_legacy/rucio.py +51 -107
- rucio/cli/bin_legacy/rucio_admin.py +26 -26
- rucio/cli/command.py +1 -0
- rucio/cli/did.py +2 -2
- rucio/cli/opendata.py +132 -0
- rucio/cli/replica.py +15 -5
- rucio/cli/rule.py +7 -2
- rucio/cli/scope.py +3 -2
- rucio/cli/utils.py +28 -4
- rucio/client/baseclient.py +9 -1
- rucio/client/client.py +2 -0
- rucio/client/diracclient.py +73 -12
- rucio/client/opendataclient.py +249 -0
- rucio/client/subscriptionclient.py +30 -0
- rucio/client/uploadclient.py +10 -13
- rucio/common/constants.py +4 -1
- rucio/common/exception.py +55 -0
- rucio/common/plugins.py +45 -8
- rucio/common/schema/generic.py +5 -3
- rucio/common/schema/generic_multi_vo.py +4 -2
- rucio/common/types.py +8 -7
- rucio/common/utils.py +176 -11
- rucio/core/dirac.py +5 -5
- rucio/core/opendata.py +744 -0
- rucio/core/rule.py +63 -8
- rucio/core/transfer.py +1 -1
- rucio/daemons/hermes/hermes.py +26 -17
- rucio/db/sqla/constants.py +6 -0
- rucio/db/sqla/migrate_repo/versions/a62db546a1f1_opendata_initial_model.py +85 -0
- rucio/db/sqla/models.py +67 -0
- rucio/db/sqla/util.py +2 -2
- rucio/gateway/dirac.py +1 -1
- rucio/gateway/opendata.py +190 -0
- rucio/gateway/subscription.py +5 -3
- rucio/rse/protocols/protocol.py +9 -5
- rucio/rse/translation.py +17 -6
- rucio/transfertool/fts3.py +1 -0
- rucio/transfertool/fts3_plugins.py +6 -1
- rucio/vcsversion.py +4 -4
- rucio/web/rest/flaskapi/v1/common.py +34 -14
- rucio/web/rest/flaskapi/v1/config.py +1 -1
- rucio/web/rest/flaskapi/v1/dids.py +447 -160
- rucio/web/rest/flaskapi/v1/heartbeats.py +1 -1
- rucio/web/rest/flaskapi/v1/identities.py +1 -1
- rucio/web/rest/flaskapi/v1/lifetime_exceptions.py +1 -1
- rucio/web/rest/flaskapi/v1/locks.py +1 -1
- rucio/web/rest/flaskapi/v1/main.py +3 -8
- rucio/web/rest/flaskapi/v1/meta_conventions.py +1 -16
- rucio/web/rest/flaskapi/v1/nongrid_traces.py +1 -1
- rucio/web/rest/flaskapi/v1/opendata.py +391 -0
- rucio/web/rest/flaskapi/v1/opendata_public.py +146 -0
- rucio/web/rest/flaskapi/v1/requests.py +1 -1
- rucio/web/rest/flaskapi/v1/rses.py +1 -1
- rucio/web/rest/flaskapi/v1/rules.py +1 -1
- rucio/web/rest/flaskapi/v1/scopes.py +1 -1
- rucio/web/rest/flaskapi/v1/subscriptions.py +6 -9
- rucio/web/rest/flaskapi/v1/traces.py +1 -1
- rucio/web/rest/flaskapi/v1/vos.py +1 -1
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/alembic.ini.template +1 -1
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/alembic_offline.ini.template +1 -1
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/rucio.cfg.template +2 -2
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/rucio_multi_vo.cfg.template +3 -3
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/requirements.server.txt +6 -3
- rucio-38.0.0rc1.data/data/rucio/tools/reset_database.py +87 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio +2 -1
- {rucio-37.7.0.dist-info → rucio-38.0.0rc1.dist-info}/METADATA +36 -36
- {rucio-37.7.0.dist-info → rucio-38.0.0rc1.dist-info}/RECORD +120 -114
- rucio/client/fileclient.py +0 -57
- rucio-37.7.0.data/data/rucio/tools/reset_database.py +0 -40
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/globus-config.yml.template +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/ldap.cfg.template +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/mail_templates/rule_approval_request.tmpl +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/mail_templates/rule_approved_admin.tmpl +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/mail_templates/rule_approved_user.tmpl +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/mail_templates/rule_denied_admin.tmpl +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/mail_templates/rule_denied_user.tmpl +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/mail_templates/rule_ok_notification.tmpl +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/rse-accounts.cfg.template +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/etc/rucio.cfg.atlas.client.template +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/tools/bootstrap.py +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/data/rucio/tools/merge_rucio_configs.py +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-abacus-account +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-abacus-collection-replica +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-abacus-rse +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-admin +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-atropos +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-auditor +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-automatix +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-bb8 +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-cache-client +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-cache-consumer +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-finisher +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-poller +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-preparer +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-receiver +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-stager +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-submitter +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-conveyor-throttler +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-dark-reaper +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-dumper +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-follower +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-hermes +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-judge-cleaner +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-judge-evaluator +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-judge-injector +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-judge-repairer +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-kronos +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-minos +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-minos-temporary-expiration +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-necromancer +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-oauth-manager +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-reaper +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-replica-recoverer +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-rse-decommissioner +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-storage-consistency-actions +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-transmogrifier +0 -0
- {rucio-37.7.0.data → rucio-38.0.0rc1.data}/scripts/rucio-undertaker +0 -0
- {rucio-37.7.0.dist-info → rucio-38.0.0rc1.dist-info}/WHEEL +0 -0
- {rucio-37.7.0.dist-info → rucio-38.0.0rc1.dist-info}/licenses/AUTHORS.rst +0 -0
- {rucio-37.7.0.dist-info → rucio-38.0.0rc1.dist-info}/licenses/LICENSE +0 -0
- {rucio-37.7.0.dist-info → rucio-38.0.0rc1.dist-info}/top_level.txt +0 -0
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import ast
|
|
16
16
|
from json import dumps
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Optional, cast
|
|
17
18
|
|
|
18
19
|
from flask import Flask, Response, request
|
|
19
20
|
|
|
@@ -37,7 +38,7 @@ from rucio.common.exception import (
|
|
|
37
38
|
UnsupportedOperation,
|
|
38
39
|
UnsupportedStatus,
|
|
39
40
|
)
|
|
40
|
-
from rucio.common.utils import APIEncoder, parse_response, render_json
|
|
41
|
+
from rucio.common.utils import APIEncoder, clone_function, parse_response, render_json
|
|
41
42
|
from rucio.db.sqla.constants import DIDType
|
|
42
43
|
from rucio.gateway.did import (
|
|
43
44
|
add_did,
|
|
@@ -72,6 +73,10 @@ from rucio.gateway.rule import list_associated_replication_rules_for_file, list_
|
|
|
72
73
|
from rucio.web.rest.flaskapi.authenticated_bp import AuthenticatedBlueprint
|
|
73
74
|
from rucio.web.rest.flaskapi.v1.common import ErrorHandlingMethodView, check_accept_header_wrapper_flask, generate_http_error_flask, json_list, json_parameters, json_parse, param_get, parse_scope_name, response_headers, try_stream
|
|
74
75
|
|
|
76
|
+
if TYPE_CHECKING:
|
|
77
|
+
|
|
78
|
+
from flask.typing import ResponseReturnValue, RouteCallable
|
|
79
|
+
|
|
75
80
|
|
|
76
81
|
class Scope(ErrorHandlingMethodView):
|
|
77
82
|
|
|
@@ -193,7 +198,7 @@ class Search(ErrorHandlingMethodView):
|
|
|
193
198
|
default: false
|
|
194
199
|
- name: recursive
|
|
195
200
|
in: query
|
|
196
|
-
description: "Recursively list
|
|
201
|
+
description: "Recursively list children."
|
|
197
202
|
schema:
|
|
198
203
|
type: boolean
|
|
199
204
|
- name: created_before
|
|
@@ -246,7 +251,7 @@ class Search(ErrorHandlingMethodView):
|
|
|
246
251
|
type: array
|
|
247
252
|
items:
|
|
248
253
|
type: object
|
|
249
|
-
description: "The name of a DID or a
|
|
254
|
+
description: "The name of a DID or a dictionary of a DID for long option."
|
|
250
255
|
401:
|
|
251
256
|
description: "Invalid Auth Token"
|
|
252
257
|
404:
|
|
@@ -273,8 +278,15 @@ class Search(ErrorHandlingMethodView):
|
|
|
273
278
|
recursive = request.args.get('recursive', type='True'.__eq__, default=False)
|
|
274
279
|
try:
|
|
275
280
|
def generate(vo):
|
|
276
|
-
for did in list_dids(scope=scope,
|
|
281
|
+
for did in list_dids(scope=scope,
|
|
282
|
+
filters=filters,
|
|
283
|
+
did_type=did_type,
|
|
284
|
+
limit=limit,
|
|
285
|
+
long=long,
|
|
286
|
+
recursive=recursive,
|
|
287
|
+
vo=vo):
|
|
277
288
|
yield dumps(did) + '\n'
|
|
289
|
+
|
|
278
290
|
return try_stream(generate(vo=request.environ['vo']))
|
|
279
291
|
except UnsupportedOperation as error:
|
|
280
292
|
return generate_http_error_flask(409, error)
|
|
@@ -417,7 +429,7 @@ class Attachments(ErrorHandlingMethodView):
|
|
|
417
429
|
description: "The name of the DID."
|
|
418
430
|
type: string
|
|
419
431
|
dids:
|
|
420
|
-
description: "The DIDs associated
|
|
432
|
+
description: "The DIDs associated with the DID."
|
|
421
433
|
type: array
|
|
422
434
|
items:
|
|
423
435
|
type: object
|
|
@@ -461,7 +473,10 @@ class Attachments(ErrorHandlingMethodView):
|
|
|
461
473
|
return generate_http_error_flask(406, exc="Invalid attachment format.")
|
|
462
474
|
|
|
463
475
|
try:
|
|
464
|
-
attach_dids_to_dids(attachments=attachments,
|
|
476
|
+
attach_dids_to_dids(attachments=attachments,
|
|
477
|
+
ignore_duplicate=ignore_duplicate,
|
|
478
|
+
issuer=request.environ['issuer'],
|
|
479
|
+
vo=request.environ['vo'])
|
|
465
480
|
except DataIdentifierNotFound as error:
|
|
466
481
|
return generate_http_error_flask(404, error)
|
|
467
482
|
except (DuplicateContent, DataIdentifierAlreadyExists, UnsupportedOperation, FileAlreadyExists) as error:
|
|
@@ -483,7 +498,7 @@ class DIDs(ErrorHandlingMethodView):
|
|
|
483
498
|
summary: Get DID
|
|
484
499
|
description: "Get a single data identifier."
|
|
485
500
|
tags:
|
|
486
|
-
- Data
|
|
501
|
+
- Data Identifiers
|
|
487
502
|
parameters:
|
|
488
503
|
- name: scope_name
|
|
489
504
|
in: path
|
|
@@ -899,7 +914,11 @@ class Attachment(ErrorHandlingMethodView):
|
|
|
899
914
|
attachments = json_parameters()
|
|
900
915
|
|
|
901
916
|
try:
|
|
902
|
-
attach_dids(scope=scope,
|
|
917
|
+
attach_dids(scope=scope,
|
|
918
|
+
name=name,
|
|
919
|
+
attachment=attachments,
|
|
920
|
+
issuer=request.environ['issuer'],
|
|
921
|
+
vo=request.environ['vo'])
|
|
903
922
|
except InvalidPath as error:
|
|
904
923
|
return generate_http_error_flask(400, error)
|
|
905
924
|
except (DataIdentifierNotFound, RSENotFound) as error:
|
|
@@ -996,7 +1015,9 @@ class AttachmentHistory(ErrorHandlingMethodView):
|
|
|
996
1015
|
content:
|
|
997
1016
|
application/x-json-stream:
|
|
998
1017
|
schema:
|
|
999
|
-
description:
|
|
1018
|
+
description: |
|
|
1019
|
+
The DIDs with their information and history.
|
|
1020
|
+
Elements are separated by new line characters.
|
|
1000
1021
|
type: array
|
|
1001
1022
|
items:
|
|
1002
1023
|
type: object
|
|
@@ -1229,7 +1250,6 @@ class BulkFiles(ErrorHandlingMethodView):
|
|
|
1229
1250
|
return try_stream(generate(vo=request.environ['vo']))
|
|
1230
1251
|
except AccessDenied as error:
|
|
1231
1252
|
return generate_http_error_flask(401, error)
|
|
1232
|
-
return 'Created', 201
|
|
1233
1253
|
|
|
1234
1254
|
|
|
1235
1255
|
class Parents(ErrorHandlingMethodView):
|
|
@@ -1441,7 +1461,7 @@ class Meta(ErrorHandlingMethodView):
|
|
|
1441
1461
|
type: boolean
|
|
1442
1462
|
default: false
|
|
1443
1463
|
- type: object
|
|
1444
|
-
description: "Schema for **Multi-key mode** (`key` not included in path)."
|
|
1464
|
+
description: "Schema for **Multi-key mode** (`key` not included in the path)."
|
|
1445
1465
|
required:
|
|
1446
1466
|
- meta
|
|
1447
1467
|
properties:
|
|
@@ -1601,68 +1621,307 @@ class Meta(ErrorHandlingMethodView):
|
|
|
1601
1621
|
|
|
1602
1622
|
|
|
1603
1623
|
class BulkDIDsMeta(ErrorHandlingMethodView):
|
|
1624
|
+
# Public modes
|
|
1625
|
+
MODE_SET = "set" # POST /bulkdidsmeta
|
|
1626
|
+
MODE_GET = "get" # POST /bulkmeta
|
|
1604
1627
|
|
|
1605
|
-
|
|
1628
|
+
# Endpoint‑specific configuration
|
|
1629
|
+
MODE_SET_DOC = \
|
|
1606
1630
|
"""
|
|
1607
1631
|
---
|
|
1608
|
-
summary:
|
|
1609
|
-
description:
|
|
1632
|
+
summary: Bulk set metadata
|
|
1633
|
+
description: |
|
|
1634
|
+
Add or update metadata for **multiple** data identifiers (DIDs) in a single request.
|
|
1635
|
+
|
|
1636
|
+
* Every array element **must** contain the DID (`scope` + `name`) and a `meta`
|
|
1637
|
+
dictionary holding the key–value pairs to insert / update for that DID.
|
|
1638
|
+
* If a key already exists the exact action (overwrite, merge, reject)
|
|
1639
|
+
depends on the metadata plug‑in configured on the server.
|
|
1640
|
+
* The operation is atomic across the whole list: the request succeeds only if
|
|
1641
|
+
all DIDs are updated successfully; otherwise no metadata is written.
|
|
1610
1642
|
tags:
|
|
1611
1643
|
- Data Identifiers
|
|
1612
1644
|
requestBody:
|
|
1645
|
+
required: true
|
|
1613
1646
|
content:
|
|
1614
|
-
|
|
1647
|
+
application/json:
|
|
1615
1648
|
schema:
|
|
1616
1649
|
type: object
|
|
1617
1650
|
required:
|
|
1618
|
-
|
|
1651
|
+
- dids
|
|
1619
1652
|
properties:
|
|
1620
1653
|
dids:
|
|
1621
|
-
description: "
|
|
1654
|
+
description: "List of DIDs with the metadata to apply."
|
|
1622
1655
|
type: array
|
|
1623
1656
|
items:
|
|
1624
|
-
description: "The DID and associated metadata."
|
|
1625
1657
|
type: object
|
|
1658
|
+
required:
|
|
1659
|
+
- scope
|
|
1660
|
+
- name
|
|
1661
|
+
- meta
|
|
1626
1662
|
properties:
|
|
1627
1663
|
scope:
|
|
1628
|
-
description: "
|
|
1664
|
+
description: "Scope of the DID."
|
|
1629
1665
|
type: string
|
|
1630
1666
|
name:
|
|
1631
|
-
description: "
|
|
1667
|
+
description: "Name of the DID."
|
|
1632
1668
|
type: string
|
|
1633
1669
|
meta:
|
|
1634
|
-
description:
|
|
1670
|
+
description: >
|
|
1671
|
+
Dictionary of metadata key–value pairs to set for this DID.
|
|
1672
|
+
Values may be strings, numbers, booleans, etc. – consult
|
|
1673
|
+
the plug‑in documentation for supported types.
|
|
1635
1674
|
type: object
|
|
1675
|
+
examples:
|
|
1676
|
+
minimal:
|
|
1677
|
+
summary: "Two DIDs, simple values"
|
|
1678
|
+
value:
|
|
1679
|
+
dids:
|
|
1680
|
+
- scope: "user"
|
|
1681
|
+
name: "dataset_001"
|
|
1682
|
+
meta:
|
|
1683
|
+
experiment: "CMS"
|
|
1684
|
+
year: 2024
|
|
1685
|
+
- scope: "user"
|
|
1686
|
+
name: "dataset_002"
|
|
1687
|
+
meta:
|
|
1688
|
+
experiment: "ATLAS"
|
|
1689
|
+
is_open: true
|
|
1690
|
+
|
|
1636
1691
|
responses:
|
|
1637
|
-
|
|
1638
|
-
description: "Created"
|
|
1692
|
+
201:
|
|
1693
|
+
description: "Created – all metadata updates were accepted."
|
|
1639
1694
|
content:
|
|
1640
|
-
|
|
1695
|
+
text/plain:
|
|
1641
1696
|
schema:
|
|
1642
1697
|
type: string
|
|
1643
1698
|
enum: ["Created"]
|
|
1699
|
+
400:
|
|
1700
|
+
description: |
|
|
1701
|
+
Bad Request – malformed JSON or missing/invalid `dids` structure.
|
|
1702
|
+
(Raised by the generic JSON‑parameter parser before reaching the
|
|
1703
|
+
business logic.)
|
|
1644
1704
|
401:
|
|
1645
|
-
description:
|
|
1705
|
+
description: |
|
|
1706
|
+
Unauthorized – invalid Auth Token or insufficient privileges to
|
|
1707
|
+
modify at least one DID.
|
|
1646
1708
|
404:
|
|
1647
|
-
description: "DID not
|
|
1648
|
-
406:
|
|
1649
|
-
description: "Not acceptable"
|
|
1709
|
+
description: "Not found – at least one DID in the request does not exist."
|
|
1650
1710
|
409:
|
|
1651
|
-
description: "
|
|
1711
|
+
description: "Conflict – the operation is not supported for at least one DID."
|
|
1712
|
+
"""
|
|
1713
|
+
|
|
1714
|
+
MODE_GET_DOC = \
|
|
1715
|
+
"""
|
|
1716
|
+
---
|
|
1717
|
+
summary: Bulk get metadata
|
|
1718
|
+
description: |
|
|
1719
|
+
Retrieve the metadata of **multiple** data identifiers (DIDs) with one request.
|
|
1720
|
+
|
|
1721
|
+
* The request body is ordinary JSON (`Content‑Type: application/json`).
|
|
1722
|
+
* The **response** is a *newline‑delimited JSON* stream
|
|
1723
|
+
(`Content‑Type: application/x-json-stream`).
|
|
1724
|
+
Each line is a complete JSON object containing the metadata of a single
|
|
1725
|
+
DID. The client **must** send `Accept: application/x-json-stream`; any
|
|
1726
|
+
other `Accept` value is rejected with **406 Not Acceptable**.
|
|
1727
|
+
* If `inherit=true`, metadata from parent containers is concatenated
|
|
1728
|
+
(plug‑in permitting).
|
|
1729
|
+
* `plugin` chooses the metadata plug‑in; `"ALL"` returns the union of every
|
|
1730
|
+
available plug‑in.
|
|
1731
|
+
|
|
1732
|
+
tags:
|
|
1733
|
+
- Data Identifiers
|
|
1734
|
+
requestBody:
|
|
1735
|
+
required: true
|
|
1736
|
+
content:
|
|
1737
|
+
application/json:
|
|
1738
|
+
schema:
|
|
1739
|
+
type: object
|
|
1740
|
+
required:
|
|
1741
|
+
- dids
|
|
1742
|
+
properties:
|
|
1743
|
+
dids:
|
|
1744
|
+
description: "List of DIDs to query."
|
|
1745
|
+
type: array
|
|
1746
|
+
items:
|
|
1747
|
+
type: object
|
|
1748
|
+
required:
|
|
1749
|
+
- scope
|
|
1750
|
+
- name
|
|
1751
|
+
properties:
|
|
1752
|
+
scope:
|
|
1753
|
+
description: "Scope of the DID."
|
|
1754
|
+
type: string
|
|
1755
|
+
name:
|
|
1756
|
+
description: "Name of the DID."
|
|
1757
|
+
type: string
|
|
1758
|
+
inherit:
|
|
1759
|
+
description: >
|
|
1760
|
+
If **true**, the server will also return metadata inherited
|
|
1761
|
+
from parent DIDs (default: `false`).
|
|
1762
|
+
type: boolean
|
|
1763
|
+
default: false
|
|
1764
|
+
plugin:
|
|
1765
|
+
description: >
|
|
1766
|
+
Which metadata plug‑in to query
|
|
1767
|
+
(`"JSON"`, `"DID_COLUMN"`, `"ALL"`, etc.; default: `"JSON"`).
|
|
1768
|
+
type: string
|
|
1769
|
+
default: "JSON"
|
|
1770
|
+
examples:
|
|
1771
|
+
defaultQuery:
|
|
1772
|
+
summary: "Query two DIDs with inheritance"
|
|
1773
|
+
value:
|
|
1774
|
+
dids:
|
|
1775
|
+
- scope: "user"
|
|
1776
|
+
name: "dataset_001"
|
|
1777
|
+
- scope: "user"
|
|
1778
|
+
name: "dataset_002"
|
|
1779
|
+
inherit: true
|
|
1780
|
+
plugin: "JSON"
|
|
1781
|
+
|
|
1782
|
+
responses:
|
|
1783
|
+
200:
|
|
1784
|
+
description: "OK – stream of newline‑delimited JSON objects, one per DID."
|
|
1785
|
+
content:
|
|
1786
|
+
application/x-json-stream:
|
|
1787
|
+
schema:
|
|
1788
|
+
type: string
|
|
1789
|
+
example: |
|
|
1790
|
+
{"scope":"user","name":"dataset_001","experiment":"CMS", ...}\n
|
|
1791
|
+
{"scope":"user","name":"dataset_002","experiment":"ATLAS", ...}\n
|
|
1792
|
+
|
|
1793
|
+
400:
|
|
1794
|
+
description: >
|
|
1795
|
+
Bad Request – cannot decode JSON parameter list (malformed body or
|
|
1796
|
+
missing `dids` array).
|
|
1797
|
+
401:
|
|
1798
|
+
description: "Unauthorized – invalid Auth Token."
|
|
1799
|
+
404:
|
|
1800
|
+
description: "Not found – none of the requested DIDs exist."
|
|
1801
|
+
406:
|
|
1802
|
+
description: |
|
|
1803
|
+
Not Acceptable – an `Accept` header was sent, but it does not
|
|
1804
|
+
include `application/x-json-stream`.
|
|
1805
|
+
"""
|
|
1806
|
+
|
|
1807
|
+
_MODE_DOC: dict[str, str] = {
|
|
1808
|
+
MODE_SET: MODE_SET_DOC,
|
|
1809
|
+
MODE_GET: MODE_GET_DOC
|
|
1810
|
+
}
|
|
1811
|
+
_MODE_ACCEPT: dict[str, Optional[list[str]]] = {
|
|
1812
|
+
MODE_SET: None,
|
|
1813
|
+
MODE_GET: ["application/x-json-stream"],
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
# cache for the on‑the‑fly subclasses
|
|
1817
|
+
_SUBCLASSES: dict[str, type["BulkDIDsMeta"]] = {}
|
|
1818
|
+
|
|
1819
|
+
def __init__(self, mode: str, *args: Any, **kwargs: Any) -> None:
|
|
1820
|
+
if mode not in (self.MODE_SET, self.MODE_GET):
|
|
1821
|
+
raise ValueError(f"Unsupported mode {mode!r}")
|
|
1822
|
+
self.mode = mode
|
|
1823
|
+
super().__init__(*args, **kwargs)
|
|
1824
|
+
|
|
1825
|
+
# Main factory
|
|
1826
|
+
@classmethod
|
|
1827
|
+
def as_view(
|
|
1828
|
+
cls,
|
|
1829
|
+
name: str,
|
|
1830
|
+
*class_args: Any,
|
|
1831
|
+
**class_kwargs: Any
|
|
1832
|
+
) -> 'RouteCallable':
|
|
1833
|
+
"""
|
|
1834
|
+
Create the Flask view function for *mode* with the correct
|
|
1835
|
+
docstring and (if required) an Accept‑header wrapper.
|
|
1652
1836
|
"""
|
|
1653
|
-
|
|
1654
|
-
|
|
1837
|
+
|
|
1838
|
+
# 0. Extract & validate the mode argument
|
|
1839
|
+
mode_opt = class_kwargs.pop("mode", None)
|
|
1840
|
+
if mode_opt not in (cls.MODE_SET, cls.MODE_GET):
|
|
1841
|
+
raise ValueError("BulkDIDsMeta.as_view() needs mode='set' or mode='get'")
|
|
1842
|
+
|
|
1843
|
+
# Tell the type checker that `mode_opt` is definitely str here
|
|
1844
|
+
mode = cast('str', mode_opt)
|
|
1845
|
+
|
|
1846
|
+
# 1. Build / fetch the dedicated subclass for this mode
|
|
1847
|
+
if mode not in cls._SUBCLASSES:
|
|
1848
|
+
sub_name = f"{cls.__name__}_{mode}"
|
|
1849
|
+
sub: type["BulkDIDsMeta"] = cast(
|
|
1850
|
+
'type[BulkDIDsMeta]',
|
|
1851
|
+
type(sub_name, (cls,), {}),
|
|
1852
|
+
)
|
|
1853
|
+
new_post = clone_function(cls.post) # independent copy
|
|
1854
|
+
new_post.__doc__ = cls._MODE_DOC[mode] # mode‑specific spec
|
|
1855
|
+
setattr(sub, "post", new_post)
|
|
1856
|
+
cls._SUBCLASSES[mode] = sub
|
|
1857
|
+
sub = cls._SUBCLASSES[mode]
|
|
1858
|
+
|
|
1859
|
+
# 2. Let MethodView build the dispatch function
|
|
1860
|
+
class_kwargs["mode"] = mode # forward to __init__
|
|
1861
|
+
|
|
1862
|
+
raw_view = super(BulkDIDsMeta, sub).as_view(
|
|
1863
|
+
name, *class_args, **class_kwargs
|
|
1864
|
+
)
|
|
1865
|
+
|
|
1866
|
+
# 3. Add Accept‑header checker when needed
|
|
1867
|
+
accept = cls._MODE_ACCEPT[mode]
|
|
1868
|
+
if accept:
|
|
1869
|
+
raw_view = check_accept_header_wrapper_flask(accept)(raw_view)
|
|
1870
|
+
|
|
1871
|
+
view_func = cast('RouteCallable', raw_view)
|
|
1872
|
+
return view_func
|
|
1873
|
+
|
|
1874
|
+
# ------------------------------------------------------------------
|
|
1875
|
+
# Single entry‑point for both logical endpoints
|
|
1876
|
+
# ------------------------------------------------------------------
|
|
1877
|
+
def post(self) -> 'ResponseReturnValue':
|
|
1878
|
+
if self.mode == self.MODE_SET:
|
|
1879
|
+
return self._handle_set()
|
|
1880
|
+
return self._handle_get()
|
|
1881
|
+
|
|
1882
|
+
# ------------------------------------------------------------------
|
|
1883
|
+
# Implementation of the SET variant (/bulkdidsmeta)
|
|
1884
|
+
# ------------------------------------------------------------------
|
|
1885
|
+
def _handle_set(self) -> 'ResponseReturnValue':
|
|
1886
|
+
params = json_parameters()
|
|
1887
|
+
dids = param_get(params, "dids")
|
|
1655
1888
|
|
|
1656
1889
|
try:
|
|
1657
|
-
set_dids_metadata_bulk(
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
except
|
|
1663
|
-
return generate_http_error_flask(
|
|
1890
|
+
set_dids_metadata_bulk(
|
|
1891
|
+
dids=dids,
|
|
1892
|
+
issuer=request.environ["issuer"],
|
|
1893
|
+
vo=request.environ["vo"],
|
|
1894
|
+
)
|
|
1895
|
+
except DataIdentifierNotFound as err:
|
|
1896
|
+
return generate_http_error_flask(404, err)
|
|
1897
|
+
except UnsupportedOperation as err:
|
|
1898
|
+
return generate_http_error_flask(409, err)
|
|
1899
|
+
except AccessDenied as err:
|
|
1900
|
+
return generate_http_error_flask(401, err)
|
|
1901
|
+
|
|
1902
|
+
return "Created", 201
|
|
1903
|
+
|
|
1904
|
+
# ------------------------------------------------------------------
|
|
1905
|
+
# Implementation of the GET variant (/bulkmeta)
|
|
1906
|
+
# ------------------------------------------------------------------
|
|
1907
|
+
def _handle_get(self) -> 'ResponseReturnValue':
|
|
1908
|
+
params = json_parameters()
|
|
1909
|
+
dids = param_get(params, "dids")
|
|
1910
|
+
inherit = param_get(params, "inherit", default=False)
|
|
1911
|
+
plugin = param_get(params, "plugin", default="JSON")
|
|
1664
1912
|
|
|
1665
|
-
|
|
1913
|
+
try:
|
|
1914
|
+
def generate(vo):
|
|
1915
|
+
for meta in get_metadata_bulk(dids, inherit=inherit, plugin=plugin, vo=vo):
|
|
1916
|
+
yield render_json(**meta) + "\n"
|
|
1917
|
+
|
|
1918
|
+
return try_stream(generate(vo=request.environ["vo"]))
|
|
1919
|
+
except ValueError as err:
|
|
1920
|
+
return generate_http_error_flask(
|
|
1921
|
+
400, err, "Cannot decode json parameter list"
|
|
1922
|
+
)
|
|
1923
|
+
except DataIdentifierNotFound as err:
|
|
1924
|
+
return generate_http_error_flask(404, err)
|
|
1666
1925
|
|
|
1667
1926
|
|
|
1668
1927
|
class Rules(ErrorHandlingMethodView):
|
|
@@ -1717,82 +1976,6 @@ class Rules(ErrorHandlingMethodView):
|
|
|
1717
1976
|
return generate_http_error_flask(404, error)
|
|
1718
1977
|
|
|
1719
1978
|
|
|
1720
|
-
class BulkMeta(ErrorHandlingMethodView):
|
|
1721
|
-
|
|
1722
|
-
@check_accept_header_wrapper_flask(['application/x-json-stream'])
|
|
1723
|
-
def post(self):
|
|
1724
|
-
"""
|
|
1725
|
-
---
|
|
1726
|
-
summary: Get metadata bulk
|
|
1727
|
-
description: "List all metadata of a list of data identifiers."
|
|
1728
|
-
tags:
|
|
1729
|
-
- Data Identifiers
|
|
1730
|
-
requestBody:
|
|
1731
|
-
content:
|
|
1732
|
-
'application/x-json-stream':
|
|
1733
|
-
schema:
|
|
1734
|
-
type: object
|
|
1735
|
-
required:
|
|
1736
|
-
- dids
|
|
1737
|
-
properties:
|
|
1738
|
-
dids:
|
|
1739
|
-
description: "The DIDs."
|
|
1740
|
-
type: array
|
|
1741
|
-
items:
|
|
1742
|
-
description: "A DID."
|
|
1743
|
-
type: object
|
|
1744
|
-
properties:
|
|
1745
|
-
name:
|
|
1746
|
-
description: "The name of the DID."
|
|
1747
|
-
type: string
|
|
1748
|
-
scope:
|
|
1749
|
-
description: "The scope of the DID."
|
|
1750
|
-
type: string
|
|
1751
|
-
inherit:
|
|
1752
|
-
description: "Concatenated the metadata of the parent if set to true."
|
|
1753
|
-
type: boolean
|
|
1754
|
-
default: false
|
|
1755
|
-
plugin:
|
|
1756
|
-
description: "The DID meta plugin to query or 'ALL' for all available plugins"
|
|
1757
|
-
type: string
|
|
1758
|
-
default: "JSON"
|
|
1759
|
-
responses:
|
|
1760
|
-
200:
|
|
1761
|
-
description: "OK"
|
|
1762
|
-
content:
|
|
1763
|
-
application/json:
|
|
1764
|
-
schema:
|
|
1765
|
-
description: "A list of metadata identifiers for the DIDs. Separated by new lines."
|
|
1766
|
-
type: array
|
|
1767
|
-
items:
|
|
1768
|
-
description: "The metadata for one DID."
|
|
1769
|
-
type: object
|
|
1770
|
-
400:
|
|
1771
|
-
description: "Cannot decode json parameter list"
|
|
1772
|
-
401:
|
|
1773
|
-
description: "Invalid Auth Token"
|
|
1774
|
-
404:
|
|
1775
|
-
description: "DID not found"
|
|
1776
|
-
406:
|
|
1777
|
-
description: "Not acceptable"
|
|
1778
|
-
"""
|
|
1779
|
-
parameters = json_parameters()
|
|
1780
|
-
dids = param_get(parameters, 'dids')
|
|
1781
|
-
inherit = param_get(parameters, 'inherit', default=False)
|
|
1782
|
-
plugin = param_get(parameters, 'plugin', default='JSON')
|
|
1783
|
-
|
|
1784
|
-
try:
|
|
1785
|
-
def generate(vo):
|
|
1786
|
-
for meta in get_metadata_bulk(dids, inherit=inherit, plugin=plugin, vo=vo):
|
|
1787
|
-
yield render_json(**meta) + '\n'
|
|
1788
|
-
|
|
1789
|
-
return try_stream(generate(vo=request.environ['vo']))
|
|
1790
|
-
except ValueError as error:
|
|
1791
|
-
return generate_http_error_flask(400, error, 'Cannot decode json parameter list')
|
|
1792
|
-
except DataIdentifierNotFound as error:
|
|
1793
|
-
return generate_http_error_flask(404, error)
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
1979
|
class AssociatedRules(ErrorHandlingMethodView):
|
|
1797
1980
|
|
|
1798
1981
|
@check_accept_header_wrapper_flask(['application/x-json-stream'])
|
|
@@ -1887,7 +2070,9 @@ class GUIDLookup(ErrorHandlingMethodView):
|
|
|
1887
2070
|
content:
|
|
1888
2071
|
application/x-json-stream:
|
|
1889
2072
|
schema:
|
|
1890
|
-
description:
|
|
2073
|
+
description: |
|
|
2074
|
+
A list of all datasets associated with the guid.
|
|
2075
|
+
Items are separated by new line character.
|
|
1891
2076
|
type: array
|
|
1892
2077
|
items:
|
|
1893
2078
|
description: "A dataset associated with a guid."
|
|
@@ -2325,60 +2510,162 @@ class Follow(ErrorHandlingMethodView):
|
|
|
2325
2510
|
account = param_get(parameters, 'account')
|
|
2326
2511
|
|
|
2327
2512
|
try:
|
|
2328
|
-
remove_did_from_followed(scope=scope,
|
|
2513
|
+
remove_did_from_followed(scope=scope,
|
|
2514
|
+
name=name,
|
|
2515
|
+
account=account,
|
|
2516
|
+
issuer=request.environ['issuer'],
|
|
2517
|
+
vo=request.environ['vo'])
|
|
2329
2518
|
except DataIdentifierNotFound as error:
|
|
2330
2519
|
return generate_http_error_flask(404, error)
|
|
2331
2520
|
|
|
2332
2521
|
return '', 200
|
|
2333
2522
|
|
|
2334
2523
|
|
|
2335
|
-
def blueprint():
|
|
2524
|
+
def blueprint() -> AuthenticatedBlueprint:
|
|
2525
|
+
"""
|
|
2526
|
+
Creates and configures an authenticated Flask Blueprint for handling various routes
|
|
2527
|
+
related to Data Identifiers (DIDs) and their associated functionalities.
|
|
2528
|
+
"""
|
|
2336
2529
|
bp = AuthenticatedBlueprint('dids', __name__, url_prefix='/dids')
|
|
2337
2530
|
|
|
2338
|
-
scope_view = Scope.as_view('scope')
|
|
2339
|
-
bp.add_url_rule('/<scope>/', view_func=scope_view, methods=['get', ])
|
|
2340
|
-
guid_lookup_view = GUIDLookup.as_view('guid_lookup')
|
|
2341
|
-
bp.add_url_rule('/<guid>/guid', view_func=guid_lookup_view, methods=['get', ])
|
|
2342
|
-
search_view = Search.as_view('search')
|
|
2343
|
-
bp.add_url_rule('/<scope>/dids/search', view_func=search_view, methods=['get', ])
|
|
2344
2531
|
dids_view = DIDs.as_view('dids')
|
|
2345
|
-
bp.add_url_rule(
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2532
|
+
bp.add_url_rule(
|
|
2533
|
+
'/<path:scope_name>/status',
|
|
2534
|
+
view_func=dids_view,
|
|
2535
|
+
methods=['put', 'get'],
|
|
2536
|
+
)
|
|
2537
|
+
bp.add_url_rule(
|
|
2538
|
+
'/<path:scope_name>',
|
|
2539
|
+
view_func=dids_view,
|
|
2540
|
+
methods=['get', 'post'],
|
|
2541
|
+
)
|
|
2542
|
+
|
|
2352
2543
|
meta_view = Meta.as_view('meta')
|
|
2353
|
-
bp.add_url_rule(
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
bp.add_url_rule(
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
bp.add_url_rule(
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
bp.add_url_rule(
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2544
|
+
bp.add_url_rule(
|
|
2545
|
+
'/<path:scope_name>/meta',
|
|
2546
|
+
defaults={'key': None},
|
|
2547
|
+
view_func=meta_view,
|
|
2548
|
+
methods=['get', 'post', 'delete'],
|
|
2549
|
+
)
|
|
2550
|
+
bp.add_url_rule(
|
|
2551
|
+
'/<path:scope_name>/meta/<key>',
|
|
2552
|
+
view_func=meta_view,
|
|
2553
|
+
methods=['post'],
|
|
2554
|
+
)
|
|
2555
|
+
|
|
2556
|
+
bp.add_url_rule(
|
|
2557
|
+
"/bulkdidsmeta",
|
|
2558
|
+
view_func=BulkDIDsMeta.as_view("bulkdidsmeta", mode=BulkDIDsMeta.MODE_SET),
|
|
2559
|
+
methods=["post"],
|
|
2560
|
+
)
|
|
2561
|
+
|
|
2562
|
+
bp.add_url_rule(
|
|
2563
|
+
"/bulkmeta",
|
|
2564
|
+
view_func=BulkDIDsMeta.as_view("bulkmeta", mode=BulkDIDsMeta.MODE_GET),
|
|
2565
|
+
methods=["post"],
|
|
2566
|
+
)
|
|
2567
|
+
|
|
2568
|
+
bp.add_url_rule(
|
|
2569
|
+
'/<path:scope_name>/dids',
|
|
2570
|
+
view_func=Attachment.as_view('attachment'),
|
|
2571
|
+
methods=['get', 'post', 'delete'],
|
|
2572
|
+
)
|
|
2573
|
+
|
|
2574
|
+
bp.add_url_rule(
|
|
2575
|
+
'/new',
|
|
2576
|
+
view_func=NewDIDs.as_view('new_dids'),
|
|
2577
|
+
methods=['get'],
|
|
2578
|
+
)
|
|
2579
|
+
|
|
2580
|
+
bp.add_url_rule(
|
|
2581
|
+
'',
|
|
2582
|
+
view_func=BulkDIDS.as_view('bulkdids'),
|
|
2583
|
+
methods=['post'],
|
|
2584
|
+
)
|
|
2585
|
+
|
|
2586
|
+
bp.add_url_rule(
|
|
2587
|
+
'/<path:scope_name>/dids/history',
|
|
2588
|
+
view_func=AttachmentHistory.as_view('attachment_history'),
|
|
2589
|
+
methods=['get'],
|
|
2590
|
+
)
|
|
2591
|
+
|
|
2592
|
+
bp.add_url_rule(
|
|
2593
|
+
'/attachments',
|
|
2594
|
+
view_func=Attachments.as_view('attachments'),
|
|
2595
|
+
methods=['post'],
|
|
2596
|
+
)
|
|
2597
|
+
|
|
2598
|
+
bp.add_url_rule(
|
|
2599
|
+
'/<scope>/dids/search',
|
|
2600
|
+
view_func=Search.as_view('search'),
|
|
2601
|
+
methods=['get'],
|
|
2602
|
+
)
|
|
2603
|
+
|
|
2604
|
+
bp.add_url_rule(
|
|
2605
|
+
'/<scope>/',
|
|
2606
|
+
view_func=Scope.as_view('scope'),
|
|
2607
|
+
methods=['get'],
|
|
2608
|
+
)
|
|
2609
|
+
|
|
2610
|
+
bp.add_url_rule(
|
|
2611
|
+
'/<guid>/guid',
|
|
2612
|
+
view_func=GUIDLookup.as_view('guid_lookup'),
|
|
2613
|
+
methods=['get'],
|
|
2614
|
+
)
|
|
2615
|
+
|
|
2616
|
+
bp.add_url_rule(
|
|
2617
|
+
'/<path:scope_name>/files',
|
|
2618
|
+
view_func=Files.as_view('files'),
|
|
2619
|
+
methods=['get'],
|
|
2620
|
+
)
|
|
2621
|
+
|
|
2622
|
+
bp.add_url_rule(
|
|
2623
|
+
'/bulkfiles',
|
|
2624
|
+
view_func=BulkFiles.as_view('bulkfiles'),
|
|
2625
|
+
methods=['post'],
|
|
2626
|
+
)
|
|
2627
|
+
|
|
2628
|
+
bp.add_url_rule(
|
|
2629
|
+
'/<path:scope_name>/rules',
|
|
2630
|
+
view_func=Rules.as_view('rules'),
|
|
2631
|
+
methods=['get'],
|
|
2632
|
+
)
|
|
2633
|
+
|
|
2634
|
+
bp.add_url_rule(
|
|
2635
|
+
'/<path:scope_name>/parents',
|
|
2636
|
+
view_func=Parents.as_view('parents'),
|
|
2637
|
+
methods=['get'],
|
|
2638
|
+
)
|
|
2639
|
+
|
|
2640
|
+
bp.add_url_rule(
|
|
2641
|
+
'/<path:scope_name>/associated_rules',
|
|
2642
|
+
view_func=AssociatedRules.as_view('associated_rules'),
|
|
2643
|
+
methods=['get'],
|
|
2644
|
+
)
|
|
2645
|
+
|
|
2646
|
+
bp.add_url_rule(
|
|
2647
|
+
'/<path:scope_name>/follow',
|
|
2648
|
+
view_func=Follow.as_view('follow'),
|
|
2649
|
+
methods=['get', 'post', 'delete'],
|
|
2650
|
+
)
|
|
2651
|
+
|
|
2652
|
+
bp.add_url_rule(
|
|
2653
|
+
'/<input_scope>/<input_name>/<output_scope>/<output_name>/<nbfiles>/sample',
|
|
2654
|
+
view_func=SampleLegacy.as_view('sample'),
|
|
2655
|
+
methods=['post'],
|
|
2656
|
+
)
|
|
2657
|
+
|
|
2658
|
+
bp.add_url_rule(
|
|
2659
|
+
'/sample',
|
|
2660
|
+
view_func=Sample.as_view('sample_new'),
|
|
2661
|
+
methods=['post'],
|
|
2662
|
+
)
|
|
2663
|
+
|
|
2664
|
+
bp.add_url_rule(
|
|
2665
|
+
'/resurrect',
|
|
2666
|
+
view_func=Resurrect.as_view('resurrect'),
|
|
2667
|
+
methods=['post'],
|
|
2668
|
+
)
|
|
2382
2669
|
|
|
2383
2670
|
bp.after_request(response_headers)
|
|
2384
2671
|
return bp
|