girder-jsonforms 2.1.2__tar.gz → 2.1.4__tar.gz
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.
- {girder_jsonforms-2.1.2/girder_jsonforms.egg-info → girder_jsonforms-2.1.4}/PKG-INFO +2 -10
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/igsn_client.py +45 -4
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/igsn_vocab.py +43 -1
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/rest/deposition.py +5 -1
- girder_jsonforms-2.1.4/girder_jsonforms/scripts/reconcile_igsn.py +118 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/web_client/dist/girder-plugin-jsonforms.umd.cjs +10 -8
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/web_client/dist/style.css +1 -1
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/igsn_registry.py +123 -4
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4/girder_jsonforms.egg-info}/PKG-INFO +2 -10
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms.egg-info/SOURCES.txt +2 -4
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms.egg-info/entry_points.txt +1 -0
- girder_jsonforms-2.1.4/pyproject.toml +75 -0
- girder_jsonforms-2.1.2/.github/workflows/release.yml +0 -102
- girder_jsonforms-2.1.2/.gitignore +0 -136
- girder_jsonforms-2.1.2/girder_jsonforms.egg-info/not-zip-safe +0 -1
- girder_jsonforms-2.1.2/setup.py +0 -67
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/LICENSE +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/README.md +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/doc/file-uploads.md +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/doc/preload.md +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/asgi.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/announcement.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/events.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/google_drive.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/jq.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/locks.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/mail.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/metadata_dates.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/orcid.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/project_helpers.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/project_tasks.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/response_cache.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/lib/timing.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_assets/caimee-shield-80.png +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/_projectsFooter.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/_projectsFooter.txt.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/_projectsHeader.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/projectDecision.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/projectDecision.txt.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/projectSubmitted.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/mail_templates/projectSubmitted.txt.mako +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/models/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/models/deposition.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/models/entry.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/models/form.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/models/project.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/rest/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/rest/aimdl.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/rest/entry.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/rest/form.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/rest/project.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/schemas/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/schemas/datacite-v4.5.json +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/scripts/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/scripts/migrate_metadata_dates.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/scripts/preview_mail.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/settings.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/__init__.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/amdee.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/folder_ops.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/handle_enumsource.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/htmax.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/orcid.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/projects.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/pull_related_ids.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms/worker_plugin/relate_entry_to_igsn.py +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms.egg-info/dependency_links.txt +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms.egg-info/requires.txt +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/girder_jsonforms.egg-info/top_level.txt +0 -0
- {girder_jsonforms-2.1.2 → girder_jsonforms-2.1.4}/setup.cfg +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: girder-jsonforms
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.4
|
|
4
4
|
Summary: Girder plugin adding forms based on JSON-editor
|
|
5
|
-
License: BSD-3-Clause
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
7
7
|
Classifier: Environment :: Web Environment
|
|
8
|
-
Classifier: License :: OSI Approved :: BSD License
|
|
9
8
|
Classifier: Operating System :: POSIX :: Linux
|
|
10
9
|
Classifier: Programming Language :: Python
|
|
11
10
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -23,14 +22,7 @@ Requires-Dist: google-auth-oauthlib
|
|
|
23
22
|
Requires-Dist: jsondiff
|
|
24
23
|
Requires-Dist: pandas
|
|
25
24
|
Requires-Dist: openpyxl
|
|
26
|
-
Dynamic: classifier
|
|
27
|
-
Dynamic: description
|
|
28
|
-
Dynamic: description-content-type
|
|
29
|
-
Dynamic: license
|
|
30
25
|
Dynamic: license-file
|
|
31
|
-
Dynamic: requires-dist
|
|
32
|
-
Dynamic: requires-python
|
|
33
|
-
Dynamic: summary
|
|
34
26
|
|
|
35
27
|
### Girder JSONForms Plugin
|
|
36
28
|
|
|
@@ -19,6 +19,7 @@ Two rules matter here:
|
|
|
19
19
|
import logging
|
|
20
20
|
|
|
21
21
|
import requests
|
|
22
|
+
from girder.exceptions import RestException
|
|
22
23
|
from girder.models.setting import Setting
|
|
23
24
|
from requests.adapters import HTTPAdapter
|
|
24
25
|
from urllib3.util.retry import Retry
|
|
@@ -29,16 +30,40 @@ logger = logging.getLogger(__name__)
|
|
|
29
30
|
|
|
30
31
|
DEFAULT_TIMEOUT = 30
|
|
31
32
|
|
|
33
|
+
# Upstream statuses that mean "the caller asked for something the registry will
|
|
34
|
+
# not do": a prefix outside the tenant's vocabulary, an identifier that already
|
|
35
|
+
# exists, a published record that cannot be withdrawn. The service's own
|
|
36
|
+
# explanation is the useful part and the person filling in the form can act on
|
|
37
|
+
# it, so these become a 400 carrying that message.
|
|
38
|
+
#
|
|
39
|
+
# Everything else -- our service token rejected, a 5xx, an unreachable host --
|
|
40
|
+
# is this deployment's problem rather than the caller's, so it becomes a 502
|
|
41
|
+
# and the detail is left to the log.
|
|
42
|
+
CLIENT_ERROR_STATUSES = frozenset({400, 404, 409, 422})
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
|
|
45
|
+
def _rest_code(status_code):
|
|
46
|
+
return 400 if status_code in CLIENT_ERROR_STATUSES else 502
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class IGSNServiceError(RestException):
|
|
34
50
|
"""The IGSN registry service could not satisfy a request.
|
|
35
51
|
|
|
36
52
|
Deliberately fatal to whatever operation raised it: a Girder deposition must
|
|
37
53
|
never exist without a matching record in the registry.
|
|
54
|
+
|
|
55
|
+
It is a ``RestException`` so that the fatality is a *useful* one. Identifier
|
|
56
|
+
allocation happens several levels below the endpoint -- in
|
|
57
|
+
``PrefixCounter.get_next``, and off the ``model.entry.save`` event for the
|
|
58
|
+
form path -- and most of those call sites have no business knowing about
|
|
59
|
+
HTTP. Left as a plain exception it reached the REST layer as an unhandled
|
|
60
|
+
error, so "Institution JH is not permitted for this tenant" showed up in the
|
|
61
|
+
UI as a bare 500. Subclassing means every path reports the registry's reason
|
|
62
|
+
without each one having to catch and translate.
|
|
38
63
|
"""
|
|
39
64
|
|
|
40
65
|
def __init__(self, message, status_code=None, detail=None):
|
|
41
|
-
super().__init__(message)
|
|
66
|
+
super().__init__(message, code=_rest_code(status_code))
|
|
42
67
|
self.status_code = status_code
|
|
43
68
|
self.detail = detail
|
|
44
69
|
|
|
@@ -82,9 +107,25 @@ class IGSNClient:
|
|
|
82
107
|
detail = response.json().get("detail")
|
|
83
108
|
except ValueError:
|
|
84
109
|
detail = response.text[:500]
|
|
110
|
+
# The verb and path are what an operator needs, so they go to the
|
|
111
|
+
# log; the message itself is shown to the user verbatim for a 4xx,
|
|
112
|
+
# where they would only be noise.
|
|
113
|
+
logger.warning(
|
|
114
|
+
"IGSN service returned %s for %s %s: %s",
|
|
115
|
+
response.status_code,
|
|
116
|
+
method,
|
|
117
|
+
path,
|
|
118
|
+
detail,
|
|
119
|
+
)
|
|
120
|
+
if response.status_code in CLIENT_ERROR_STATUSES:
|
|
121
|
+
message = f"IGSN registry rejected the request: {detail}"
|
|
122
|
+
else:
|
|
123
|
+
message = (
|
|
124
|
+
f"IGSN service returned {response.status_code} for "
|
|
125
|
+
f"{method} {path}: {detail}"
|
|
126
|
+
)
|
|
85
127
|
raise IGSNServiceError(
|
|
86
|
-
|
|
87
|
-
f"{method} {path}: {detail}",
|
|
128
|
+
message,
|
|
88
129
|
status_code=response.status_code,
|
|
89
130
|
detail=detail,
|
|
90
131
|
)
|
|
@@ -10,6 +10,13 @@ answer briefly. The local settings remain the fallback for local mode and for
|
|
|
10
10
|
when the service is momentarily unreachable: rejecting a prefix that was valid
|
|
11
11
|
five minutes ago is worse than using a slightly stale vocabulary, since the
|
|
12
12
|
service validates every allocation again anyway.
|
|
13
|
+
|
|
14
|
+
The registry's vocabulary is global but allocation is tenant-scoped, so the two
|
|
15
|
+
are not the same list. ``GET /vocabularies`` returns every institution the
|
|
16
|
+
registry knows; ``GET /whoami`` says which of them this instance's tenant may
|
|
17
|
+
allocate under. Without the intersection the form happily offers JH to a tenant
|
|
18
|
+
that only holds AB, and the refusal arrives after the metadata has been filled
|
|
19
|
+
in -- readable now, but still a dead end.
|
|
13
20
|
"""
|
|
14
21
|
|
|
15
22
|
import logging
|
|
@@ -43,6 +50,41 @@ def invalidate():
|
|
|
43
50
|
_cache["value"] = None
|
|
44
51
|
|
|
45
52
|
|
|
53
|
+
def _permitted(institutions, client):
|
|
54
|
+
"""Narrow the registry's institutions to the ones this tenant may use.
|
|
55
|
+
|
|
56
|
+
An empty ``allowed_institutions`` means unrestricted -- the service reads it
|
|
57
|
+
that way too (``validate_prefix`` only checks when the list is non-empty),
|
|
58
|
+
and the two must agree or the form offers less than the registry accepts.
|
|
59
|
+
|
|
60
|
+
A ``whoami`` that fails is not worth discarding a good vocabulary over: the
|
|
61
|
+
unfiltered list is what was shown before, and the allocation is validated
|
|
62
|
+
again at the service anyway.
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
allowed = set((client.whoami() or {}).get("allowed_institutions") or ())
|
|
66
|
+
except IGSNServiceError:
|
|
67
|
+
logger.exception("Could not read the tenant from the IGSN service")
|
|
68
|
+
return institutions
|
|
69
|
+
|
|
70
|
+
if not allowed:
|
|
71
|
+
return institutions
|
|
72
|
+
|
|
73
|
+
permitted = {
|
|
74
|
+
code: value for code, value in institutions.items() if code in allowed
|
|
75
|
+
}
|
|
76
|
+
if not permitted:
|
|
77
|
+
# Nothing the tenant is allowed to use exists in the vocabulary, so the
|
|
78
|
+
# dropdown will be empty. That is the honest answer -- every prefix
|
|
79
|
+
# would be refused -- but it is a misconfiguration, not a normal state.
|
|
80
|
+
logger.warning(
|
|
81
|
+
"Tenant is allowed institutions %s, none of which the IGSN "
|
|
82
|
+
"vocabulary defines",
|
|
83
|
+
sorted(allowed),
|
|
84
|
+
)
|
|
85
|
+
return permitted
|
|
86
|
+
|
|
87
|
+
|
|
46
88
|
def get_vocabularies():
|
|
47
89
|
"""Return ``{"institutions": ..., "materials": ...}``."""
|
|
48
90
|
client = None
|
|
@@ -62,7 +104,7 @@ def get_vocabularies():
|
|
|
62
104
|
try:
|
|
63
105
|
remote = client.vocabularies()
|
|
64
106
|
value = {
|
|
65
|
-
"institutions": remote["institutions"],
|
|
107
|
+
"institutions": _permitted(remote["institutions"], client),
|
|
66
108
|
"materials": remote["materials"],
|
|
67
109
|
}
|
|
68
110
|
except (IGSNServiceError, KeyError):
|
|
@@ -350,7 +350,11 @@ class Deposition(Resource):
|
|
|
350
350
|
except pymongo.errors.BulkWriteError as e:
|
|
351
351
|
raise RestException(f"Error creating child depositions: {e.details}")
|
|
352
352
|
except IGSNServiceError as e:
|
|
353
|
-
|
|
353
|
+
# Re-raised only to name the operation; ``e`` already carries the
|
|
354
|
+
# registry's reason and the status it should surface as.
|
|
355
|
+
raise RestException(
|
|
356
|
+
f"IGSN registry rejected the batch: {e.detail or e}", code=e.code
|
|
357
|
+
)
|
|
354
358
|
return DepositionModel().load(
|
|
355
359
|
result.inserted_ids[0], user=self.getCurrentUser()
|
|
356
360
|
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Pull IGSN registry state onto local depositions.
|
|
2
|
+
|
|
3
|
+
Publication is asynchronous the whole way down: Girder queues it with the
|
|
4
|
+
registry, the registry queues it with DataCite, and nothing pushes the outcome
|
|
5
|
+
back. ``publish_deposition`` polls for a while after queuing one, which covers
|
|
6
|
+
the ordinary case, but not a record published straight against the registry's
|
|
7
|
+
API, not one that settled while Girder was down, and not one that took longer
|
|
8
|
+
than that poll chain was willing to wait. This is the backstop for those, and
|
|
9
|
+
is meant to run on a timer::
|
|
10
|
+
|
|
11
|
+
*/15 * * * * girder-jsonforms-reconcile-igsn --quiet
|
|
12
|
+
|
|
13
|
+
It runs the ``reconcile`` task in-process -- no broker, no worker -- against
|
|
14
|
+
whatever database the local Girder configuration points at, so it is also the
|
|
15
|
+
quickest way to repair one record by hand.
|
|
16
|
+
|
|
17
|
+
By default it only looks at depositions that have not settled locally, which is
|
|
18
|
+
what a timer is for and what keeps ``--limit`` meaningful: the scan has no
|
|
19
|
+
ordering, so a flat limit over every deposition would re-check the same first
|
|
20
|
+
thousand for ever. ``--all`` does the full pass, which is the one that also
|
|
21
|
+
reports depositions the registry has never heard of.
|
|
22
|
+
|
|
23
|
+
Usage::
|
|
24
|
+
|
|
25
|
+
girder-jsonforms-reconcile-igsn # unsettled records
|
|
26
|
+
girder-jsonforms-reconcile-igsn --all # full audit, to --limit
|
|
27
|
+
girder-jsonforms-reconcile-igsn --prefix JHAMAL
|
|
28
|
+
girder-jsonforms-reconcile-igsn --igsn JHAMAL00001 --igsn JHAMAL00002
|
|
29
|
+
girder-jsonforms-reconcile-igsn --quiet # summary only on trouble
|
|
30
|
+
|
|
31
|
+
Exits non-zero when a record could not be read from the registry, so a cron
|
|
32
|
+
run that hit a broken service says so. A deposition the registry has never
|
|
33
|
+
heard of is logged and counted, but is not a failure: it is a finding for a
|
|
34
|
+
human, and reconcile deliberately does not invent the missing record.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
import argparse
|
|
38
|
+
import logging
|
|
39
|
+
import sys
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger("girder_jsonforms.reconcile_igsn")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def main(argv=None):
|
|
45
|
+
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--prefix",
|
|
48
|
+
help="Only reconcile depositions whose IGSN starts with this prefix.",
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"--igsn",
|
|
52
|
+
action="append",
|
|
53
|
+
dest="igsns",
|
|
54
|
+
metavar="IGSN",
|
|
55
|
+
help="Reconcile one IGSN. Repeatable; overrides --prefix.",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"--all",
|
|
59
|
+
action="store_true",
|
|
60
|
+
dest="check_all",
|
|
61
|
+
help=(
|
|
62
|
+
"Also re-check records that have already settled locally, and "
|
|
63
|
+
"report any the registry does not have."
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--limit",
|
|
68
|
+
type=int,
|
|
69
|
+
default=1000,
|
|
70
|
+
help="Maximum number of depositions to check (default: 1000).",
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"-q",
|
|
74
|
+
"--quiet",
|
|
75
|
+
action="store_true",
|
|
76
|
+
help="Log warnings and errors only -- a quiet cron run stays quiet.",
|
|
77
|
+
)
|
|
78
|
+
parser.add_argument("-v", "--verbose", action="store_true", help="Debug logging.")
|
|
79
|
+
args = parser.parse_args(argv)
|
|
80
|
+
|
|
81
|
+
level = logging.INFO
|
|
82
|
+
if args.quiet:
|
|
83
|
+
level = logging.WARNING
|
|
84
|
+
if args.verbose:
|
|
85
|
+
level = logging.DEBUG
|
|
86
|
+
logging.basicConfig(level=level, format="%(levelname)s %(message)s")
|
|
87
|
+
|
|
88
|
+
# Imported here so ``--help`` works without a configured Girder/DB.
|
|
89
|
+
from girder_jsonforms.worker_plugin.igsn_registry import reconcile
|
|
90
|
+
|
|
91
|
+
summary = reconcile(
|
|
92
|
+
prefix=args.prefix,
|
|
93
|
+
igsns=args.igsns,
|
|
94
|
+
limit=args.limit,
|
|
95
|
+
pending_only=not args.check_all,
|
|
96
|
+
)
|
|
97
|
+
if summary is None:
|
|
98
|
+
# Local mode. Not an error: an instance that allocates its own IGSNs has
|
|
99
|
+
# no registry to reconcile against, and a cron entry left behind after
|
|
100
|
+
# switching modes should not start failing.
|
|
101
|
+
logger.warning("No IGSN registry is configured; nothing to reconcile.")
|
|
102
|
+
return 0
|
|
103
|
+
|
|
104
|
+
logger.info(
|
|
105
|
+
"Reconciled: %(updated)s updated, %(unchanged)s unchanged, "
|
|
106
|
+
"%(missing)s missing, %(errors)s error(s).",
|
|
107
|
+
summary,
|
|
108
|
+
)
|
|
109
|
+
if summary["missing"]:
|
|
110
|
+
logger.warning(
|
|
111
|
+
"%s deposition(s) are not in the registry; see the lines above.",
|
|
112
|
+
summary["missing"],
|
|
113
|
+
)
|
|
114
|
+
return 1 if summary["errors"] else 0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
if __name__ == "__main__":
|
|
118
|
+
sys.exit(main())
|