sws-api-client 2.26.0__tar.gz → 3.1.0__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.
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/PKG-INFO +24 -1
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/README.md +23 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/pyproject.toml +2 -1
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/__init__.py +1 -1
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/campaign_manager.py +64 -4
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/schemas/validation-report.schema.json +63 -4
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/tags.py +1 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/.gitignore +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/LICENSE +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/auth.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/case_agnostic_model.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/codelist.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/data_retrieval.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/datasets.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/datatable.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/db.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/discover.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/files.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/flaglist.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/generic_models.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/harvester.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/mail.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/metadata_instances.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/plugins.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/s3.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/sessions.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/sws_api_client.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/tasks.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/token.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/users.py +0 -0
- {sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/validation_report.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sws_api_client
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Author-email: Matteo <matteo.terrinoni@fao.org>, Daniele <daniele.mansillo@fao.org>, Luca <luca.simi@fao.org>
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: <4,>=3.10
|
|
@@ -16,6 +16,29 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
|
|
17
17
|
This library provides the user with a set of useful tools to easily interact with the FAO SWS (Statistical Working System) REST APIs.
|
|
18
18
|
|
|
19
|
+
## Campaign validation outputs
|
|
20
|
+
|
|
21
|
+
Campaign files use top-level `visibility: shared | internal`, defaulting to shared. Pass
|
|
22
|
+
`visibility="internal"` to `create_file` before uploading internal content. `CampaignFile` reads
|
|
23
|
+
`visibility` and `validation_report_id`; `RunOutput` uses `visibility` instead of `audience`.
|
|
24
|
+
The migration is `audience="both"` → `visibility="shared"`, and
|
|
25
|
+
`audience="internal"` → `visibility="internal"`.
|
|
26
|
+
|
|
27
|
+
`register_validation_output(team_id, report_id, source_file_id, descriptor)` registers an internal
|
|
28
|
+
artifact of the exact report. `validation_outputs` lists it and `download_content_to_file` streams it.
|
|
29
|
+
Every manual harvesting event names one file. Worker methods `claim_manual_harvest`,
|
|
30
|
+
`heartbeat_manual_harvest`, `manual_harvest_child_task` and `complete_manual_harvest` use the file ID,
|
|
31
|
+
request ID and wrapper task ID. Record the child immediately after launch; completion has one outcome.
|
|
32
|
+
Statistical writes use the existing `Datasets.import_file_via_plugin` and
|
|
33
|
+
`Datatables.import_file_via_plugin` methods to start separate Save Data tasks.
|
|
34
|
+
|
|
35
|
+
The packaged report schema supports a DATA ZIP with root `data.csv` and optional observation
|
|
36
|
+
`metadata.csv`, plus explicit separator/quote options. METADATA outputs are independent datatable
|
|
37
|
+
inputs; separate report files are not implicitly paired.
|
|
38
|
+
|
|
39
|
+
Pass `payload.cycleId` from `ValidationTriggered` to `complete_validation(cycle_id=...)`, including
|
|
40
|
+
when the plugin fails. This lets CM reject an old run's completion after a validation restart.
|
|
41
|
+
|
|
19
42
|
## Installation
|
|
20
43
|
|
|
21
44
|
The module is available on Pypi:
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
This library provides the user with a set of useful tools to easily interact with the FAO SWS (Statistical Working System) REST APIs.
|
|
4
4
|
|
|
5
|
+
## Campaign validation outputs
|
|
6
|
+
|
|
7
|
+
Campaign files use top-level `visibility: shared | internal`, defaulting to shared. Pass
|
|
8
|
+
`visibility="internal"` to `create_file` before uploading internal content. `CampaignFile` reads
|
|
9
|
+
`visibility` and `validation_report_id`; `RunOutput` uses `visibility` instead of `audience`.
|
|
10
|
+
The migration is `audience="both"` → `visibility="shared"`, and
|
|
11
|
+
`audience="internal"` → `visibility="internal"`.
|
|
12
|
+
|
|
13
|
+
`register_validation_output(team_id, report_id, source_file_id, descriptor)` registers an internal
|
|
14
|
+
artifact of the exact report. `validation_outputs` lists it and `download_content_to_file` streams it.
|
|
15
|
+
Every manual harvesting event names one file. Worker methods `claim_manual_harvest`,
|
|
16
|
+
`heartbeat_manual_harvest`, `manual_harvest_child_task` and `complete_manual_harvest` use the file ID,
|
|
17
|
+
request ID and wrapper task ID. Record the child immediately after launch; completion has one outcome.
|
|
18
|
+
Statistical writes use the existing `Datasets.import_file_via_plugin` and
|
|
19
|
+
`Datatables.import_file_via_plugin` methods to start separate Save Data tasks.
|
|
20
|
+
|
|
21
|
+
The packaged report schema supports a DATA ZIP with root `data.csv` and optional observation
|
|
22
|
+
`metadata.csv`, plus explicit separator/quote options. METADATA outputs are independent datatable
|
|
23
|
+
inputs; separate report files are not implicitly paired.
|
|
24
|
+
|
|
25
|
+
Pass `payload.cycleId` from `ValidationTriggered` to `complete_validation(cycle_id=...)`, including
|
|
26
|
+
when the plugin fails. This lets CM reject an old run's completion after a validation restart.
|
|
27
|
+
|
|
5
28
|
## Installation
|
|
6
29
|
|
|
7
30
|
The module is available on Pypi:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sws_api_client"
|
|
3
|
-
version = "
|
|
3
|
+
version = "3.1.0"
|
|
4
4
|
description = ""
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Matteo", email = "matteo.terrinoni@fao.org" },
|
|
@@ -28,6 +28,7 @@ dev = [
|
|
|
28
28
|
"pytest>=8.3.2,<9",
|
|
29
29
|
"pytest-cov>=5.0.0,<6",
|
|
30
30
|
"pytest-mock>=3.14.0,<4",
|
|
31
|
+
"jsonschema>=4,<5",
|
|
31
32
|
]
|
|
32
33
|
|
|
33
34
|
[build-system]
|
|
@@ -107,17 +107,17 @@ class RunOutput:
|
|
|
107
107
|
Attributes:
|
|
108
108
|
file_id (int | str): the file already registered and uploaded
|
|
109
109
|
role (str): what it is within the run — ``report``, ``annotated``, ``log``, …
|
|
110
|
-
|
|
110
|
+
visibility (str): ``internal`` keeps it on the campaign side; ``shared`` lets the country read
|
|
111
111
|
it. UNMARKED MEANS READABLE, so say ``internal`` when it is: the day this is forgotten,
|
|
112
112
|
a country seeing a log is a smaller failure than a country losing a document it is owed
|
|
113
113
|
"""
|
|
114
114
|
|
|
115
115
|
file_id: Union[int, str]
|
|
116
116
|
role: str = "report"
|
|
117
|
-
|
|
117
|
+
visibility: str = "shared"
|
|
118
118
|
|
|
119
119
|
def to_wire(self) -> Dict[str, Any]:
|
|
120
|
-
return {"fileId": self.file_id, "role": self.role, "
|
|
120
|
+
return {"fileId": self.file_id, "role": self.role, "visibility": self.visibility}
|
|
121
121
|
|
|
122
122
|
|
|
123
123
|
@dataclass
|
|
@@ -187,6 +187,8 @@ class CampaignFile:
|
|
|
187
187
|
stage: Optional[str] = None
|
|
188
188
|
storage_uri: Optional[str] = None
|
|
189
189
|
raw: Dict[str, Any] = field(default_factory=dict)
|
|
190
|
+
visibility: str = "shared"
|
|
191
|
+
validation_report_id: Optional[int] = None
|
|
190
192
|
|
|
191
193
|
@classmethod
|
|
192
194
|
def from_wire(cls, raw: Dict[str, Any]) -> "CampaignFile":
|
|
@@ -199,6 +201,8 @@ class CampaignFile:
|
|
|
199
201
|
stage=raw.get("stage"),
|
|
200
202
|
storage_uri=raw.get("storageUri"),
|
|
201
203
|
raw=raw,
|
|
204
|
+
visibility=raw.get("visibility", "shared"),
|
|
205
|
+
validation_report_id=raw.get("validationReportId"),
|
|
202
206
|
)
|
|
203
207
|
|
|
204
208
|
|
|
@@ -321,6 +325,7 @@ class CampaignManager:
|
|
|
321
325
|
country: Optional[str] = None,
|
|
322
326
|
questionnaire_type_id: Optional[Union[int, str]] = None,
|
|
323
327
|
stage: Optional[str] = "original",
|
|
328
|
+
visibility: str = "shared",
|
|
324
329
|
) -> CampaignFile:
|
|
325
330
|
"""Register a file of the campaign — the row, before its bytes.
|
|
326
331
|
|
|
@@ -330,7 +335,9 @@ class CampaignManager:
|
|
|
330
335
|
a new version of the same chain. So a generation run may be repeated without leaving a
|
|
331
336
|
country's case showing the same questionnaire four times, and no plugin has to be idempotent.
|
|
332
337
|
"""
|
|
333
|
-
|
|
338
|
+
if visibility not in ("shared", "internal"):
|
|
339
|
+
raise ValueError("visibility must be shared or internal")
|
|
340
|
+
body: Dict[str, Any] = {"campaignId": campaign_id, "name": name, "type": type, "visibility": visibility}
|
|
334
341
|
if country:
|
|
335
342
|
body["country"] = country
|
|
336
343
|
if questionnaire_type_id is not None:
|
|
@@ -340,6 +347,39 @@ class CampaignManager:
|
|
|
340
347
|
raw = self._call("POST", "/api/v1/files", params={"teamId": team_id}, json_body=body)
|
|
341
348
|
return CampaignFile.from_wire(raw)
|
|
342
349
|
|
|
350
|
+
def register_validation_output(
|
|
351
|
+
self, team_id: Union[int, str], report_id: Union[int, str],
|
|
352
|
+
source_file_id: Union[int, str], descriptor: Dict[str, Any],
|
|
353
|
+
) -> CampaignFile:
|
|
354
|
+
"""Register an internal artifact of an exact report, preserving its schema descriptor."""
|
|
355
|
+
raw = self._call("POST", f"/api/v1/files/{report_id}/outputs", params={"teamId": team_id},
|
|
356
|
+
json_body={"sourceFileId": source_file_id, "descriptor": descriptor})
|
|
357
|
+
return CampaignFile.from_wire(raw)
|
|
358
|
+
|
|
359
|
+
def validation_outputs(self, team_id: Union[int, str], report_id: Union[int, str]) -> List[CampaignFile]:
|
|
360
|
+
"""List internal artifacts of one report (including historical reports)."""
|
|
361
|
+
return [CampaignFile.from_wire(raw) for raw in self._call(
|
|
362
|
+
"GET", f"/api/v1/files/{report_id}/outputs", params={"teamId": team_id})]
|
|
363
|
+
|
|
364
|
+
def claim_manual_harvest(self, team_id, file_id, request_id, task_id, expires_at):
|
|
365
|
+
"""Claim one file's request; duplicate deliveries receive HTTP 409."""
|
|
366
|
+
return self._call("POST", f"/api/v1/files/{file_id}/manual-harvest/processing", params={"teamId": team_id},
|
|
367
|
+
json_body={"requestId": request_id, "taskId": task_id, "expiresAt": expires_at})
|
|
368
|
+
|
|
369
|
+
def heartbeat_manual_harvest(self, team_id, file_id, request_id, task_id, expires_at):
|
|
370
|
+
return self._call("PUT", f"/api/v1/files/{file_id}/manual-harvest/status", params={"teamId": team_id},
|
|
371
|
+
json_body={"requestId": request_id, "taskId": task_id, "expiresAt": expires_at})
|
|
372
|
+
|
|
373
|
+
def manual_harvest_child_task(self, team_id, file_id, request_id, task_id, child_task_id, destination):
|
|
374
|
+
"""Record the Save Data task before waiting for it, allowing recovery after wrapper failure."""
|
|
375
|
+
return self._call("POST", f"/api/v1/files/{file_id}/manual-harvest/child-task", params={"teamId": team_id},
|
|
376
|
+
json_body={"requestId": request_id, "taskId": task_id, "childTaskId": child_task_id, "destination": destination})
|
|
377
|
+
|
|
378
|
+
def complete_manual_harvest(self, team_id, file_id, request_id, task_id, outcome, reason=None, result=None):
|
|
379
|
+
return self._call("POST", f"/api/v1/files/{file_id}/manual-harvest/complete", params={"teamId": team_id},
|
|
380
|
+
json_body={"requestId": request_id, "taskId": task_id, "outcome": outcome,
|
|
381
|
+
"reason": reason, "result": result or {}})
|
|
382
|
+
|
|
343
383
|
def upload_content(
|
|
344
384
|
self,
|
|
345
385
|
team_id: Union[int, str],
|
|
@@ -387,6 +427,20 @@ class CampaignManager:
|
|
|
387
427
|
)
|
|
388
428
|
return response.content
|
|
389
429
|
|
|
430
|
+
def download_content_to_file(self, team_id: Union[int, str], file_id: Union[int, str],
|
|
431
|
+
path: Union[str, Path]) -> Path:
|
|
432
|
+
"""Stream a stored artifact to disk without materialising the whole file in memory."""
|
|
433
|
+
destination = Path(path)
|
|
434
|
+
with self.sws_client.discoverable.get(
|
|
435
|
+
self.endpoint, f"/api/v1/files/{file_id}/content", params={"teamId": team_id},
|
|
436
|
+
options={"raw_response": True, "raise_on_error": True}, stream=True,
|
|
437
|
+
) as response:
|
|
438
|
+
with destination.open("wb") as out:
|
|
439
|
+
for chunk in response.iter_content(chunk_size=1024 * 1024):
|
|
440
|
+
if chunk:
|
|
441
|
+
out.write(chunk)
|
|
442
|
+
return destination
|
|
443
|
+
|
|
390
444
|
# ── validation ────────────────────────────────────────────────────────────
|
|
391
445
|
|
|
392
446
|
def claim_validation(
|
|
@@ -433,6 +487,7 @@ class CampaignManager:
|
|
|
433
487
|
issues: Optional[List[ValidationIssue]] = None,
|
|
434
488
|
reason: Optional[str] = None,
|
|
435
489
|
outputs: Optional[List["RunOutput"]] = None,
|
|
490
|
+
cycle_id: Optional[str] = None,
|
|
436
491
|
) -> Dict[str, Any]:
|
|
437
492
|
"""How the check ended, with the figures and the issues a reader will see.
|
|
438
493
|
|
|
@@ -446,6 +501,10 @@ class CampaignManager:
|
|
|
446
501
|
|
|
447
502
|
``outputs`` is everything ELSE the run left behind — an annotated workbook, a log — each with
|
|
448
503
|
its role and its reader. ``file_report_id`` remains the one a screen opens first.
|
|
504
|
+
|
|
505
|
+
Pass the trigger's ``payload.cycleId`` as ``cycle_id`` on both success and failure. CM
|
|
506
|
+
rejects a completion from an earlier validation after a restart. Omit it only for legacy
|
|
507
|
+
invocations that supplied no cycle identifier.
|
|
449
508
|
"""
|
|
450
509
|
return self._call(
|
|
451
510
|
"POST",
|
|
@@ -458,6 +517,7 @@ class CampaignManager:
|
|
|
458
517
|
"errors": [i.to_wire() for i in (issues or [])],
|
|
459
518
|
"reason": reason,
|
|
460
519
|
"outputs": [o.to_wire() for o in (outputs or [])] or None,
|
|
520
|
+
**({"cycleId": cycle_id} if cycle_id is not None else {}),
|
|
461
521
|
},
|
|
462
522
|
)
|
|
463
523
|
|
{sws_api_client-2.26.0 → sws_api_client-3.1.0}/sws_api_client/schemas/validation-report.schema.json
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
3
|
"$id": "validation-schema-v5.5",
|
|
4
|
-
"$comment": "v5.5 = v5.4 with the corrections agreed with the Campaign Manager team, 2026-09-01. (1) Every $ref pointed at fao-validation-plugin-proposal-schema-v5.3, so nothing resolved: they now point into this document. (2) findings[].required was [class, code, location, severity, values]; a code is optional because it is a team's own finer statement, a location is absent when the finding is about something that is not there, and values are absent when it is about no value in particular (a count). It is now [class, severity]. (3) The version patterns read ^\\\\d+\\\\.\\\\d+[\\\\.\\\\d+]?$, whose character class rejected 1.2.3 and accepted 1.2+. (4) submission.file.name rejected spaces and accents, which real questionnaire filenames have. (5) questionnaire.reference_years.items had a minimum and no type. Still open, deliberately not changed here: objects are not closed (additionalProperties), so a misspelt field validates silently; and Output enumerates ten near-identical allOf branches where an if/then on filetype would say the same thing once. (6) Six more required fields a plugin cannot always know: root.outputs (a run that leaves nothing has none), questionnaire.language and .version, submission.submitted_by and .submitted_at (who sent it is the platform's fact, not the validator's), and file.size. (7) questionnaire.kind had minLength 3, which rejects AQ; file.id had minLength 8, which rejects the platform's own ids. A required field a producer must invent is a field that will be invented. (8) findings[].values items were typed [string, integer], which rejects null
|
|
4
|
+
"$comment": "v5.5 = v5.4 with the corrections agreed with the Campaign Manager team, 2026-09-01. (1) Every $ref pointed at fao-validation-plugin-proposal-schema-v5.3, so nothing resolved: they now point into this document. (2) findings[].required was [class, code, location, severity, values]; a code is optional because it is a team's own finer statement, a location is absent when the finding is about something that is not there, and values are absent when it is about no value in particular (a count). It is now [class, severity]. (3) The version patterns read ^\\\\d+\\\\.\\\\d+[\\\\.\\\\d+]?$, whose character class rejected 1.2.3 and accepted 1.2+. (4) submission.file.name rejected spaces and accents, which real questionnaire filenames have. (5) questionnaire.reference_years.items had a minimum and no type. Still open, deliberately not changed here: objects are not closed (additionalProperties), so a misspelt field validates silently; and Output enumerates ten near-identical allOf branches where an if/then on filetype would say the same thing once. (6) Six more required fields a plugin cannot always know: root.outputs (a run that leaves nothing has none), questionnaire.language and .version, submission.submitted_by and .submitted_at (who sent it is the platform's fact, not the validator's), and file.size. (7) questionnaire.kind had minLength 3, which rejects AQ; file.id had minLength 8, which rejects the platform's own ids. A required field a producer must invent is a field that will be invented. (8) findings[].values items were typed [string, integer], which rejects null \u2014 the value a finding about a MISSING entry carries \u2014 and every decimal. Carrying bad data is this document's job, so they are unconstrained.",
|
|
5
5
|
"type": "object",
|
|
6
6
|
"required": [
|
|
7
7
|
"findings",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"values": {
|
|
68
|
-
"description": "The values the finding is about: the figure out of range, the flag nobody recognises, null where something required is missing. Deliberately unconstrained
|
|
68
|
+
"description": "The values the finding is about: the figure out of range, the flag nobody recognises, null where something required is missing. Deliberately unconstrained \u2014 reporting that a value is invalid means carrying the invalid value, so a schema that rejects the document when the data is bad makes it impossible to say the data is bad.",
|
|
69
69
|
"type": "array",
|
|
70
70
|
"items": {
|
|
71
71
|
"type": [
|
|
@@ -515,7 +515,7 @@
|
|
|
515
515
|
"type": "string"
|
|
516
516
|
},
|
|
517
517
|
"kind": {
|
|
518
|
-
"
|
|
518
|
+
"const": "DATATABLE"
|
|
519
519
|
}
|
|
520
520
|
}
|
|
521
521
|
}
|
|
@@ -821,7 +821,7 @@
|
|
|
821
821
|
"type": "string"
|
|
822
822
|
},
|
|
823
823
|
"kind": {
|
|
824
|
-
"
|
|
824
|
+
"const": "DATATABLE"
|
|
825
825
|
}
|
|
826
826
|
}
|
|
827
827
|
}
|
|
@@ -960,6 +960,65 @@
|
|
|
960
960
|
}
|
|
961
961
|
}
|
|
962
962
|
]
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
"type": "object",
|
|
966
|
+
"description": "One dataset import package: data.csv and optional observation metadata.csv at the archive root; CSV options apply to both entries.",
|
|
967
|
+
"required": [
|
|
968
|
+
"id",
|
|
969
|
+
"kind",
|
|
970
|
+
"filetype",
|
|
971
|
+
"role",
|
|
972
|
+
"target",
|
|
973
|
+
"separator",
|
|
974
|
+
"delimiter"
|
|
975
|
+
],
|
|
976
|
+
"properties": {
|
|
977
|
+
"id": {
|
|
978
|
+
"type": "string"
|
|
979
|
+
},
|
|
980
|
+
"kind": {
|
|
981
|
+
"const": "FILE"
|
|
982
|
+
},
|
|
983
|
+
"filetype": {
|
|
984
|
+
"const": "ZIP"
|
|
985
|
+
},
|
|
986
|
+
"role": {
|
|
987
|
+
"const": "DATA"
|
|
988
|
+
},
|
|
989
|
+
"separator": {
|
|
990
|
+
"enum": [
|
|
991
|
+
"\t",
|
|
992
|
+
",",
|
|
993
|
+
";"
|
|
994
|
+
]
|
|
995
|
+
},
|
|
996
|
+
"delimiter": {
|
|
997
|
+
"enum": [
|
|
998
|
+
"\"",
|
|
999
|
+
"'",
|
|
1000
|
+
"`"
|
|
1001
|
+
]
|
|
1002
|
+
},
|
|
1003
|
+
"target": {
|
|
1004
|
+
"type": "object",
|
|
1005
|
+
"required": [
|
|
1006
|
+
"kind",
|
|
1007
|
+
"id"
|
|
1008
|
+
],
|
|
1009
|
+
"properties": {
|
|
1010
|
+
"kind": {
|
|
1011
|
+
"enum": [
|
|
1012
|
+
"DATASET",
|
|
1013
|
+
"SESSION"
|
|
1014
|
+
]
|
|
1015
|
+
},
|
|
1016
|
+
"id": {
|
|
1017
|
+
"type": "string"
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
963
1022
|
}
|
|
964
1023
|
]
|
|
965
1024
|
},
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|