deriva 1.7.0__tar.gz → 1.7.1__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.
- deriva-1.7.1/CHANGELOG.md +18 -0
- {deriva-1.7.0/deriva.egg-info → deriva-1.7.1}/PKG-INFO +2 -2
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/__init__.py +1 -1
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/catalog_cli.py +83 -39
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/datapath.py +314 -3
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/__init__.py +4 -2
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/__init__.py +4 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/deriva_download.py +33 -13
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/deriva_download_cli.py +3 -2
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/query/base_query_processor.py +9 -4
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/__init__.py +4 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/deriva_upload.py +4 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/deriva_upload_cli.py +2 -2
- {deriva-1.7.0 → deriva-1.7.1/deriva.egg-info}/PKG-INFO +2 -2
- {deriva-1.7.0 → deriva-1.7.1}/deriva.egg-info/requires.txt +1 -1
- {deriva-1.7.0 → deriva-1.7.1}/setup.py +1 -1
- {deriva-1.7.0 → deriva-1.7.1}/tests/deriva/core/test_datapath.py +24 -2
- deriva-1.7.0/CHANGELOG.md +0 -11
- {deriva-1.7.0 → deriva-1.7.1}/.gitignore +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/LICENSE +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/README.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/acl_config.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/annotation_config.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/annotation_validate.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/base_config.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/dump_catalog_annotations.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/examples/group_owner_policy.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/examples/self_serve_policy.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/config/rollback_annotation.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/annotation.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/base_cli.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/deriva_binding.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/deriva_server.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/ermrest_catalog.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/ermrest_model.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/hatrac_cli.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/hatrac_store.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/polling_ermrest_catalog.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/app_links.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/asset.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/bulk_upload.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/chaise_config.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/citation.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/column_display.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/display.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/export.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/export_2019.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/foreign_key.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/generated.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/immutable.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/indexing_preferences.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/key_display.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/non_deletable.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/required.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/source_definitions.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/table_alternatives.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/table_display.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/visible_columns.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/schemas/visible_foreign_keys.schema.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/tests/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/core_utils.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/globus_auth_utils.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/hash_utils.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/mime_utils.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/version_utils.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/core/utils/webauthn_utils.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/seo/README.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/seo/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/seo/sitemap_builder.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/seo/sitemap_cli.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/backup/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/backup/__main__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/backup/deriva_backup.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/backup/deriva_backup_cli.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/__main__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/base_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/postprocess/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/postprocess/identifier_post_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/postprocess/transfer_post_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/postprocess/url_post_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/query/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/query/bag_fetch_query_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/query/file_download_query_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/base_transform_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/column_transform_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/fasta_transform_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/format_transform_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/geo_transform_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/processors/transform/string_transform_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test1.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test10.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test11.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test12.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test13.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test14.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test15.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test16.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test19.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test2.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test20.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test3.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test4.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test5.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test6.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test7.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test8.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/download/tests/test9.json +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/restore/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/restore/__main__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/restore/deriva_restore.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/restore/deriva_restore_cli.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/__main__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/processors/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/processors/archive_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/processors/base_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/processors/logging_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/processors/metadata_update_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/processors/rename_processor.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/transfer/upload/tests/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva/utils/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva.egg-info/SOURCES.txt +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva.egg-info/dependency_links.txt +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva.egg-info/entry_points.txt +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/deriva.egg-info/top_level.txt +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/BUILD.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/Makefile +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/README.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/_static/README.txt +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.config.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.core.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.core.utils.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.seo.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.backup.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.download.processors.postprocess.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.download.processors.query.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.download.processors.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.download.processors.transform.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.download.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.restore.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/api/deriva.transfer.upload.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/commands.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-acl-config.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-annotation-config.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-annotation-validate.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-backup-cli.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-download-cli.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-hatrac-cli.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-restore-cli.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/cli/deriva-sitemap-cli.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/conf.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-catalog-snapshot.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-catalog.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-datapath-example-1.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-datapath-example-2.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-datapath-example-3.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-datapath-example-4.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/derivapy-datapath-update.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/get-started.ipynb +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/index.rst +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/install.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/make.bat +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/project-tutorial.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/docs/using-r.md +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/requirements_dev.txt +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/setup.cfg +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/tests/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/tests/deriva/__init__.py +0 -0
- {deriva-1.7.0 → deriva-1.7.1}/tests/deriva/core/__init__.py +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# CHANGE LOG
|
|
2
|
+
|
|
3
|
+
## 1.6.5
|
|
4
|
+
|
|
5
|
+
DataPath feature enhancements and changes:
|
|
6
|
+
* new `denormalize()` method that uses a `visible-columns` "context" or a heuristic function to denormalize a path expression.
|
|
7
|
+
* new `limit()` method as a chaining operation so that clients don't need to explicity `fetch()` results with limits.
|
|
8
|
+
* subtle change to `link()` method for how it determines whether an alias name has been "bound" to the path. Now, it more explicitly checks if an alias object is found in the path's bound table instances. This change also relaxes the alias naming constraint to allow for automatic conflict resolution by appending an incremental numeric disambiguator if the given alias name is already in use. This could result in some `link()` methods now succeeding where they would have failed in the past.
|
|
9
|
+
|
|
10
|
+
## 1.0.0
|
|
11
|
+
Feature release.
|
|
12
|
+
* Refactored catalog configuration API.
|
|
13
|
+
* Support for aggregate functions and binning in DataPath API.
|
|
14
|
+
* Various bug fixes.
|
|
15
|
+
* Complete change list [here](https://github.com/informatics-isi-edu/deriva-py/compare/v0.9.0...v1.0.0).
|
|
16
|
+
|
|
17
|
+
Change lists for previous releases can be found on the GitHub releases
|
|
18
|
+
page [here](https://github.com/informatics-isi-edu/deriva-py/releases).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: deriva
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.1
|
|
4
4
|
Summary: Python APIs and CLIs (Command-Line Interfaces) for the DERIVA platform.
|
|
5
5
|
Home-page: https://github.com/informatics-isi-edu/deriva-py
|
|
6
6
|
Author: USC Information Sciences Institute, Informatics Systems Research Division
|
|
@@ -29,7 +29,7 @@ Requires-Dist: certifi
|
|
|
29
29
|
Requires-Dist: pika
|
|
30
30
|
Requires-Dist: urllib3<3,>=1.26
|
|
31
31
|
Requires-Dist: portalocker>=1.2.1
|
|
32
|
-
Requires-Dist: bdbag>=1.7.
|
|
32
|
+
Requires-Dist: bdbag>=1.7.3
|
|
33
33
|
Requires-Dist: globus_sdk<4,>=3
|
|
34
34
|
Requires-Dist: fair-research-login>=0.3.1
|
|
35
35
|
Requires-Dist: fair-identifiers-client>=0.5.1
|
|
@@ -55,21 +55,21 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
55
55
|
|
|
56
56
|
# parent arg parser
|
|
57
57
|
self.remove_options(['--config-file', '--credential-file'])
|
|
58
|
-
self.parser.add_argument("--protocol", choices=["http", "https"], default='https',
|
|
58
|
+
self.parser.add_argument("-p", "--protocol", choices=["http", "https"], default='https',
|
|
59
59
|
help="transport protocol: 'http' or 'https'")
|
|
60
60
|
subparsers = self.parser.add_subparsers(title='sub-commands', dest='subcmd')
|
|
61
61
|
|
|
62
62
|
# exists parser
|
|
63
63
|
exists_parser = subparsers.add_parser('exists', help="Check if catalog exists.")
|
|
64
|
-
exists_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
64
|
+
exists_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
65
65
|
exists_parser.set_defaults(func=self.catalog_exists)
|
|
66
66
|
|
|
67
67
|
# create parser
|
|
68
68
|
create_parser = subparsers.add_parser('create', help="Create a new catalog.")
|
|
69
|
-
create_parser.add_argument("--id", metavar="<id>", type=str, help="
|
|
70
|
-
create_parser.add_argument("--owner", metavar="<owner> <owner> ...",
|
|
69
|
+
create_parser.add_argument("--id", metavar="<id>", type=str, help="Catalog ID")
|
|
70
|
+
create_parser.add_argument("-o", "--owner", metavar="<owner> <owner> ...",
|
|
71
71
|
nargs="+", help="List of quoted user or group identifier strings.")
|
|
72
|
-
create_parser.add_argument("--auto-configure", action="store_true",
|
|
72
|
+
create_parser.add_argument("-a", "--auto-configure", action="store_true",
|
|
73
73
|
help="Configure the new catalog with a set of baseline defaults")
|
|
74
74
|
create_parser.add_argument("--configure-args", metavar="[key=value key=value ...]",
|
|
75
75
|
nargs='+', action=KeyValuePairArgs, default={},
|
|
@@ -80,11 +80,12 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
80
80
|
|
|
81
81
|
# get parser
|
|
82
82
|
get_parser = subparsers.add_parser('get', help="Send a HTTP GET request to the catalog.")
|
|
83
|
-
get_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
83
|
+
get_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
84
84
|
get_parser.add_argument("path", metavar="<request-path>", help="The ERMRest API path.")
|
|
85
|
-
get_parser.add_argument("--output-file", metavar="<output file path>", help="Path to output file.")
|
|
86
|
-
get_parser.add_argument("--output-format", choices=["json", "json-stream", "csv"], default="json"
|
|
87
|
-
|
|
85
|
+
get_parser.add_argument("-o", "--output-file", metavar="<output file path>", help="Path to output file.")
|
|
86
|
+
get_parser.add_argument("-f", "--output-format", choices=["json", "json-stream", "csv"], default="json",
|
|
87
|
+
help="The output file format. Defaults to 'json'")
|
|
88
|
+
get_parser.add_argument("-a", "--auto-delete", action="store_true",
|
|
88
89
|
help="Automatically delete output file if no results are returned.")
|
|
89
90
|
get_parser.add_argument("--headers", metavar="[key=value key=value ...]",
|
|
90
91
|
nargs='+', action=KeyValuePairArgs, default={},
|
|
@@ -95,10 +96,12 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
95
96
|
|
|
96
97
|
# put parser
|
|
97
98
|
put_parser = subparsers.add_parser('put', help="Send a HTTP PUT request to the catalog.")
|
|
98
|
-
put_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
99
|
+
put_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
99
100
|
put_parser.add_argument("path", metavar="<request-path>", help="The ERMRest API path.")
|
|
100
|
-
put_parser.add_argument("
|
|
101
|
-
|
|
101
|
+
put_parser.add_argument("input-file", metavar="<input file path>",
|
|
102
|
+
help="Path to an input file containing the request message body.")
|
|
103
|
+
put_parser.add_argument("-f", "--input-format", choices=["json", "json-stream", "csv"], default="json",
|
|
104
|
+
help="The input file format. Defaults to 'json'")
|
|
102
105
|
put_parser.add_argument("--headers", metavar="[key=value key=value ...]",
|
|
103
106
|
nargs='+', action=KeyValuePairArgs, default={},
|
|
104
107
|
help="Variable length of whitespace-delimited key=value pair arguments used for "
|
|
@@ -108,10 +111,12 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
108
111
|
|
|
109
112
|
# post parser
|
|
110
113
|
post_parser = subparsers.add_parser('post', help="Send a HTTP POST request to the catalog.")
|
|
111
|
-
post_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
114
|
+
post_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
112
115
|
post_parser.add_argument("path", metavar="<request-path>", help="The ERMRest API path.")
|
|
113
|
-
post_parser.add_argument("
|
|
114
|
-
|
|
116
|
+
post_parser.add_argument("input-file", metavar="<input file path>",
|
|
117
|
+
help="Path to an input file containing the request message body.")
|
|
118
|
+
post_parser.add_argument("-f", "--input-format", choices=["json", "json-stream", "csv"], default="json",
|
|
119
|
+
help="The input file format. Defaults to 'json'")
|
|
115
120
|
post_parser.add_argument("--headers", metavar="[key=value key=value ...]",
|
|
116
121
|
nargs='+', action=KeyValuePairArgs, default={},
|
|
117
122
|
help="Variable length of whitespace-delimited key=value pair arguments used for "
|
|
@@ -122,7 +127,7 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
122
127
|
# delete parser
|
|
123
128
|
del_parser = subparsers.add_parser('delete', help="Send a HTTP DELETE request to the catalog. "
|
|
124
129
|
"Use the 'drop' command to delete the entire catalog.")
|
|
125
|
-
del_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
130
|
+
del_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
126
131
|
del_parser.add_argument("path", metavar="<request-path>", help="The ERMRest API path.")
|
|
127
132
|
del_parser.add_argument("--headers", metavar="[key=value key=value ...]",
|
|
128
133
|
nargs='+', action=KeyValuePairArgs, default={},
|
|
@@ -133,12 +138,12 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
133
138
|
|
|
134
139
|
# drop parser
|
|
135
140
|
drop_parser = subparsers.add_parser('drop', help="Delete a catalog.")
|
|
136
|
-
drop_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
141
|
+
drop_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
137
142
|
drop_parser.set_defaults(func=self.catalog_drop)
|
|
138
143
|
|
|
139
144
|
# clone parser
|
|
140
145
|
clone_parser = subparsers.add_parser('clone', help="Clone a source catalog to a new destination catalog.")
|
|
141
|
-
clone_parser.add_argument("id", metavar="<id>", type=str, help="
|
|
146
|
+
clone_parser.add_argument("id", metavar="<id>", type=str, help="Catalog ID")
|
|
142
147
|
clone_parser.add_argument("--no-copy-data", action="store_false",
|
|
143
148
|
help="Do not copy table contents.")
|
|
144
149
|
clone_parser.add_argument("--no-copy-annotations", action="store_false",
|
|
@@ -154,8 +159,8 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
154
159
|
# create_alias parser
|
|
155
160
|
create_alias_parser = subparsers.add_parser('create-alias', help="Create a new catalog alias")
|
|
156
161
|
create_alias_parser.add_argument("--id", metavar="<id>", type=str, help="The alias id.")
|
|
157
|
-
create_alias_parser.add_argument("--alias-target", metavar="<alias>", help="The target catalog id.")
|
|
158
|
-
create_alias_parser.add_argument("--owner", metavar="<owner> <owner> ...",
|
|
162
|
+
create_alias_parser.add_argument("-t", "--alias-target", metavar="<alias>", help="The target catalog id.")
|
|
163
|
+
create_alias_parser.add_argument("-o", "--owner", metavar="<owner> <owner> ...",
|
|
159
164
|
nargs="+", help="List of quoted user or group identifier strings.")
|
|
160
165
|
create_alias_parser.set_defaults(func=self.catalog_alias_create)
|
|
161
166
|
|
|
@@ -167,10 +172,10 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
167
172
|
# update_alias parser
|
|
168
173
|
update_alias_parser = subparsers.add_parser('update-alias', help="Update an existing catalog alias")
|
|
169
174
|
update_alias_parser.add_argument("--id", metavar="<id>", type=str, help="The alias id.")
|
|
170
|
-
update_alias_parser.add_argument("--alias-target", metavar="<alias>", nargs='?', default=nochange, const=None,
|
|
175
|
+
update_alias_parser.add_argument("-t", "--alias-target", metavar="<alias>", nargs='?', default=nochange, const=None,
|
|
171
176
|
help="The target catalog id. If specified without a catalog id as an argument "
|
|
172
177
|
"value, the existing alias target will be cleared ")
|
|
173
|
-
update_alias_parser.add_argument("--owner", metavar="<owner> <owner> ...", nargs='+', default=nochange,
|
|
178
|
+
update_alias_parser.add_argument("-o", "--owner", metavar="<owner> <owner> ...", nargs='+', default=nochange,
|
|
174
179
|
help="List of quoted user or group identifier strings.")
|
|
175
180
|
update_alias_parser.set_defaults(func=self.catalog_alias_update)
|
|
176
181
|
|
|
@@ -194,9 +199,29 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
194
199
|
self.id = args.id
|
|
195
200
|
self.server = DerivaServer(self.protocol,
|
|
196
201
|
args.host,
|
|
197
|
-
credentials=DerivaCatalogCLI._get_credential(
|
|
198
|
-
|
|
199
|
-
|
|
202
|
+
credentials=DerivaCatalogCLI._get_credential(
|
|
203
|
+
self.host,
|
|
204
|
+
token=args.token,
|
|
205
|
+
oauth2_token=args.oauth2_token))
|
|
206
|
+
|
|
207
|
+
@staticmethod
|
|
208
|
+
def _decorate_headers(headers, file_format, method="get"):
|
|
209
|
+
|
|
210
|
+
header_format_map = {
|
|
211
|
+
"json": "application/json",
|
|
212
|
+
"json-stream": "application/x-json-stream",
|
|
213
|
+
"csv": "text/csv"
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
format_type = header_format_map.get(file_format)
|
|
217
|
+
if format_type is None:
|
|
218
|
+
raise UsageException("Unsupported format: %s" % file_format)
|
|
219
|
+
if str(method).lower() in ["get", "head"]:
|
|
220
|
+
headers["accept"] = format_type
|
|
221
|
+
elif str(method).lower() in ["post", "put"]:
|
|
222
|
+
headers["content-type"] = format_type
|
|
223
|
+
else:
|
|
224
|
+
raise UsageException("Unsupported method: %s" % method)
|
|
200
225
|
|
|
201
226
|
def catalog_exists(self, args):
|
|
202
227
|
"""Implements the catalog_exists sub-command.
|
|
@@ -250,16 +275,7 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
250
275
|
"""
|
|
251
276
|
headers = DEFAULT_HEADERS.copy()
|
|
252
277
|
headers.update(args.headers)
|
|
253
|
-
|
|
254
|
-
if args.output_format == "json":
|
|
255
|
-
headers["accept"] = "application/json"
|
|
256
|
-
elif args.output_format == "json-stream":
|
|
257
|
-
headers["accept"] = "application/x-json-stream"
|
|
258
|
-
elif args.output_format == "csv":
|
|
259
|
-
headers["accept"] = "text/csv"
|
|
260
|
-
else:
|
|
261
|
-
raise UsageException("Unsupported output format: %s" % args.output_format)
|
|
262
|
-
|
|
278
|
+
self._decorate_headers(headers, args.output_format)
|
|
263
279
|
catalog = self.server.connect_ermrest(args.id)
|
|
264
280
|
try:
|
|
265
281
|
if args.output_file:
|
|
@@ -278,13 +294,41 @@ class DerivaCatalogCLI (BaseCLI):
|
|
|
278
294
|
os.remove(args.output_file)
|
|
279
295
|
raise
|
|
280
296
|
|
|
281
|
-
# TODO: implement PUT at some point
|
|
282
297
|
def catalog_put(self, args):
|
|
283
|
-
|
|
298
|
+
"""Implements the catalog_put sub-command.
|
|
299
|
+
"""
|
|
300
|
+
headers = DEFAULT_HEADERS.copy()
|
|
301
|
+
headers.update(args.headers)
|
|
302
|
+
self._decorate_headers(headers, args.input_format, "put")
|
|
303
|
+
try:
|
|
304
|
+
catalog = self.server.connect_ermrest(args.id)
|
|
305
|
+
with open(args.input_file, "rb") as input_file:
|
|
306
|
+
resp = catalog.put(args.path, data=input_file, headers=headers)
|
|
307
|
+
if not args.quiet:
|
|
308
|
+
pp(resp.json())
|
|
309
|
+
except HTTPError as e:
|
|
310
|
+
if e.response.status_code == requests.codes.not_found:
|
|
311
|
+
raise ResourceException('Catalog not found', e)
|
|
312
|
+
else:
|
|
313
|
+
raise e
|
|
284
314
|
|
|
285
|
-
# TODO: implement POST at some point
|
|
286
315
|
def catalog_post(self, args):
|
|
287
|
-
|
|
316
|
+
"""Implements the catalog_post sub-command.
|
|
317
|
+
"""
|
|
318
|
+
headers = DEFAULT_HEADERS.copy()
|
|
319
|
+
headers.update(args.headers)
|
|
320
|
+
self._decorate_headers(headers, args.input_format, "post")
|
|
321
|
+
try:
|
|
322
|
+
catalog = self.server.connect_ermrest(args.id)
|
|
323
|
+
with open(args.input_file, "rb") as input_file:
|
|
324
|
+
resp = catalog.post(args.path, data=input_file, headers=headers)
|
|
325
|
+
if not args.quiet:
|
|
326
|
+
pp(resp.json())
|
|
327
|
+
except HTTPError as e:
|
|
328
|
+
if e.response.status_code == requests.codes.not_found:
|
|
329
|
+
raise ResourceException('Catalog not found', e)
|
|
330
|
+
else:
|
|
331
|
+
raise e
|
|
288
332
|
|
|
289
333
|
def catalog_delete(self, args):
|
|
290
334
|
"""Implements the catalog_delete sub-command.
|
|
@@ -395,7 +395,8 @@ class DataPath (object):
|
|
|
395
395
|
By default links use inner join semantics on the foreign key / key equality comparison. The `join_type`
|
|
396
396
|
parameter can be used to specify `left`, `right`, or `full` outer join semantics.
|
|
397
397
|
|
|
398
|
-
:param right: the right hand table of the link expression
|
|
398
|
+
:param right: the right hand table of the link expression; if the table or alias name is in use, an incremental
|
|
399
|
+
number will be used to disambiguate tables instances of the same original name.
|
|
399
400
|
:param on: an equality comparison between key and foreign key columns, a conjunction of such comparisons, or a foreign key object
|
|
400
401
|
:param join_type: the join type of this link which may be 'left', 'right', 'full' outer joins or '' for inner
|
|
401
402
|
join link by default.
|
|
@@ -413,7 +414,7 @@ class DataPath (object):
|
|
|
413
414
|
raise ValueError("'on' must be specified for outer joins")
|
|
414
415
|
if right._schema._catalog != self._root._schema._catalog:
|
|
415
416
|
raise ValueError("'right' is from a different catalog. Cannot link across catalogs.")
|
|
416
|
-
if isinstance(right, _TableAlias) and right.
|
|
417
|
+
if isinstance(right, _TableAlias) and right._parent == self:
|
|
417
418
|
raise ValueError("'right' is a table alias that has already been used.")
|
|
418
419
|
else:
|
|
419
420
|
# Generate an unused alias name for the table
|
|
@@ -606,6 +607,18 @@ class DataPath (object):
|
|
|
606
607
|
|
|
607
608
|
return self
|
|
608
609
|
|
|
610
|
+
def denormalize(self, context_name=None, heuristic=None, groupkey_name='RID'):
|
|
611
|
+
"""Denormalizes a path based on a visible-columns annotation 'context' or a heuristic approach.
|
|
612
|
+
|
|
613
|
+
This method does not mutate this object. It returns a result set representing the denormalization of the path.
|
|
614
|
+
|
|
615
|
+
:param context_name: name of the visible-columns context or if none given, will attempt apply heuristics
|
|
616
|
+
:param heuristic: heuristic to apply if no context name specified
|
|
617
|
+
:param groupkey_name: column name for the group by key of the generated query expression (default: 'RID')
|
|
618
|
+
:return: a results set.
|
|
619
|
+
"""
|
|
620
|
+
return _datapath_denormalize(self, context_name=context_name, heuristic=heuristic, groupkey_name=groupkey_name)
|
|
621
|
+
|
|
609
622
|
|
|
610
623
|
class _ResultSet (object):
|
|
611
624
|
"""A set of results for various queries or data manipulations.
|
|
@@ -623,6 +636,7 @@ class _ResultSet (object):
|
|
|
623
636
|
self._fetcher_fn = fetcher_fn
|
|
624
637
|
self._results_doc = None
|
|
625
638
|
self._sort_keys = None
|
|
639
|
+
self._limit = None
|
|
626
640
|
self.uri = uri
|
|
627
641
|
|
|
628
642
|
@property
|
|
@@ -656,6 +670,19 @@ class _ResultSet (object):
|
|
|
656
670
|
self._results_doc = None
|
|
657
671
|
return self
|
|
658
672
|
|
|
673
|
+
def limit(self, n):
|
|
674
|
+
"""Set a limit on the number of results to be returned.
|
|
675
|
+
|
|
676
|
+
:param n: integer or None.
|
|
677
|
+
:return: self
|
|
678
|
+
"""
|
|
679
|
+
try:
|
|
680
|
+
self._limit = None if n is None else int(n)
|
|
681
|
+
self._results_doc = None
|
|
682
|
+
return self
|
|
683
|
+
except ValueError:
|
|
684
|
+
raise ValueError('limit argument "n" must be an integer or None')
|
|
685
|
+
|
|
659
686
|
def fetch(self, limit=None, headers=DEFAULT_HEADERS):
|
|
660
687
|
"""Fetches the results from the catalog.
|
|
661
688
|
|
|
@@ -663,7 +690,7 @@ class _ResultSet (object):
|
|
|
663
690
|
:param headers: headers to send in request to server
|
|
664
691
|
:return: self
|
|
665
692
|
"""
|
|
666
|
-
limit = int(limit) if limit else
|
|
693
|
+
limit = int(limit) if limit else self._limit
|
|
667
694
|
self._results_doc = self._fetcher_fn(limit, self._sort_keys, headers)
|
|
668
695
|
logger.debug("Fetched %d entities" % len(self._results_doc))
|
|
669
696
|
return self
|
|
@@ -797,6 +824,18 @@ class _TableWrapper (object):
|
|
|
797
824
|
"""
|
|
798
825
|
return _AttributeGroup(self, self._query, keys)
|
|
799
826
|
|
|
827
|
+
def denormalize(self, context_name=None, heuristic=None, groupkey_name='RID'):
|
|
828
|
+
"""Denormalizes a path based on a visible-columns annotation 'context' or a heuristic approach.
|
|
829
|
+
|
|
830
|
+
This method does not mutate this object. It returns a result set representing the denormalization of the path.
|
|
831
|
+
|
|
832
|
+
:param context_name: name of the visible-columns context or if none given, will attempt apply heuristics
|
|
833
|
+
:param heuristic: heuristic to apply if no context name specified
|
|
834
|
+
:param groupkey_name: column name for the group by key of the generated query expression (default: 'RID')
|
|
835
|
+
:return: a results set.
|
|
836
|
+
"""
|
|
837
|
+
return self.path.denormalize(context_name=context_name, heuristic=heuristic, groupkey_name=groupkey_name)
|
|
838
|
+
|
|
800
839
|
def insert(self, entities, defaults=set(), nondefaults=set(), add_system_defaults=True, on_conflict_skip=False):
|
|
801
840
|
"""Inserts entities into the table.
|
|
802
841
|
|
|
@@ -1760,3 +1799,275 @@ class _AttributeGroup (object):
|
|
|
1760
1799
|
bin.maxval = result.get('maxval', bin.maxval)
|
|
1761
1800
|
if (bin.minval is None) or (bin.maxval is None):
|
|
1762
1801
|
raise ValueError('Automatic determination of binning bounds failed.')
|
|
1802
|
+
|
|
1803
|
+
##
|
|
1804
|
+
## UTILITIES FOR DENORMALIZATION ##############################################
|
|
1805
|
+
##
|
|
1806
|
+
|
|
1807
|
+
def _datapath_left_outer_join_by_fkey(path, fk, alias_name=None):
|
|
1808
|
+
"""Link a table to the path based on a foreign key reference.
|
|
1809
|
+
|
|
1810
|
+
:param path: a DataPath object
|
|
1811
|
+
:param fk: an ermrest_model.ForeignKey object
|
|
1812
|
+
:param alias_name: an optional 'alias' name to use for the foreign table
|
|
1813
|
+
"""
|
|
1814
|
+
assert isinstance(path, DataPath)
|
|
1815
|
+
assert isinstance(fk, _erm.ForeignKey)
|
|
1816
|
+
catalog = path._root._schema._catalog
|
|
1817
|
+
|
|
1818
|
+
# determine 'direction' -- inbound or outbound
|
|
1819
|
+
path_context_table = path.context._base_table._wrapped_table
|
|
1820
|
+
if (path_context_table.schema.name, path_context_table.name) == (fk.table.schema.name, fk.table.name):
|
|
1821
|
+
right = catalog.schemas[fk.pk_table.schema.name].tables[fk.pk_table.name]
|
|
1822
|
+
fkcols = zip(fk.foreign_key_columns, fk.referenced_columns)
|
|
1823
|
+
elif (path_context_table.schema.name, path_context_table.name) == (fk.pk_table.schema.name, fk.pk_table.name):
|
|
1824
|
+
right = catalog.schemas[fk.table.schema.name].tables[fk.table.name]
|
|
1825
|
+
fkcols = zip(fk.referenced_columns, fk.foreign_key_columns)
|
|
1826
|
+
else:
|
|
1827
|
+
raise ValueError('Context table "%s" not referenced by foreign key "%s"' % (path_context_table.name, fk.constraint_name))
|
|
1828
|
+
|
|
1829
|
+
# compose join condition
|
|
1830
|
+
on = None
|
|
1831
|
+
for lcol, rcol in fkcols:
|
|
1832
|
+
lcol = catalog.schemas[lcol.table.schema.name].tables[lcol.table.name].columns[lcol.name]
|
|
1833
|
+
rcol = catalog.schemas[rcol.table.schema.name].tables[rcol.table.name].columns[rcol.name]
|
|
1834
|
+
if on:
|
|
1835
|
+
on = on & (lcol == rcol)
|
|
1836
|
+
else:
|
|
1837
|
+
on = lcol == rcol
|
|
1838
|
+
|
|
1839
|
+
# link
|
|
1840
|
+
path.link(right.alias(alias_name) if alias_name else right, on=on, join_type='left')
|
|
1841
|
+
|
|
1842
|
+
|
|
1843
|
+
def _datapath_deserialize_vizcolumn(path, vizcol, sources=None):
|
|
1844
|
+
"""Deserializes a visual column specification.
|
|
1845
|
+
|
|
1846
|
+
If the visible column specifies a foreign key path, the datapath object
|
|
1847
|
+
will be changed by linking the foreign keys in the path.
|
|
1848
|
+
|
|
1849
|
+
:param path: a datapath object
|
|
1850
|
+
:param vizcol: a visible column specification
|
|
1851
|
+
:return: the element to be projected from the datapath or None
|
|
1852
|
+
"""
|
|
1853
|
+
assert isinstance(path, DataPath)
|
|
1854
|
+
sources = sources if sources else {}
|
|
1855
|
+
context = path.context
|
|
1856
|
+
table = context._wrapped_table
|
|
1857
|
+
model = table.schema.model
|
|
1858
|
+
|
|
1859
|
+
if isinstance(vizcol, str):
|
|
1860
|
+
# column name specification
|
|
1861
|
+
return context.columns[vizcol]
|
|
1862
|
+
elif isinstance(vizcol, list):
|
|
1863
|
+
# constraint specification
|
|
1864
|
+
try:
|
|
1865
|
+
fk = model.fkey(vizcol)
|
|
1866
|
+
_datapath_left_outer_join_by_fkey(path, fk, alias_name='F')
|
|
1867
|
+
return ArrayD(path.context).alias(path.context._name) # project all attributes
|
|
1868
|
+
except KeyError as e:
|
|
1869
|
+
raise ValueError('Invalid foreign key constraint name: %s. If this is a key constraint name, note that keys are not supported at this time.' % str(e))
|
|
1870
|
+
elif isinstance(vizcol, dict):
|
|
1871
|
+
# resolve visible column
|
|
1872
|
+
while 'sourcekey' in vizcol:
|
|
1873
|
+
temp = sources.get(vizcol['sourcekey'], {})
|
|
1874
|
+
if temp == vizcol:
|
|
1875
|
+
raise ValueError('Visible column self reference for sourcekey "%s"' % vizcol['sourcekey'])
|
|
1876
|
+
vizcol = temp
|
|
1877
|
+
# deserialize source definition
|
|
1878
|
+
source = vizcol.get('source')
|
|
1879
|
+
if not source:
|
|
1880
|
+
# case: none
|
|
1881
|
+
raise ValueError('Could not resolve source definition for visible column')
|
|
1882
|
+
elif isinstance(source, str):
|
|
1883
|
+
# case: column name
|
|
1884
|
+
return context.columns[source]
|
|
1885
|
+
elif isinstance(source, list):
|
|
1886
|
+
# case: path expression
|
|
1887
|
+
# ...validate syntax
|
|
1888
|
+
if not all(isinstance(obj, dict) for obj in source[:-1]):
|
|
1889
|
+
raise ValueError('Source path element must be a foreign key dict')
|
|
1890
|
+
if not isinstance(source[-1], str):
|
|
1891
|
+
raise ValueError('Source path must terminate in a column name string')
|
|
1892
|
+
# link path elements by fkey; and track whether path is outbound only fkeys
|
|
1893
|
+
outbound_only = True
|
|
1894
|
+
for path_elem in source[:-1]:
|
|
1895
|
+
try:
|
|
1896
|
+
fk = model.fkey(path_elem.get('inbound', path_elem.get('outbound')))
|
|
1897
|
+
_datapath_left_outer_join_by_fkey(path, fk, alias_name='F')
|
|
1898
|
+
outbound_only = outbound_only and 'outbound' in path_elem
|
|
1899
|
+
except KeyError as e:
|
|
1900
|
+
raise ValueError('Invalid foreign key constraint name: %s' % str(e))
|
|
1901
|
+
# return terminating column or entity
|
|
1902
|
+
# ...get terminal name
|
|
1903
|
+
terminal = source[-1]
|
|
1904
|
+
# ...get alias name
|
|
1905
|
+
alias = vizcol.get('markdown_name', vizcol.get('name', path.context._name + '_' + terminal))
|
|
1906
|
+
# ...get aggregate function
|
|
1907
|
+
aggregate = {
|
|
1908
|
+
'min': Min,
|
|
1909
|
+
'max': Max,
|
|
1910
|
+
'cnt': Cnt,
|
|
1911
|
+
'cnd_d': CntD,
|
|
1912
|
+
'array': Array,
|
|
1913
|
+
'array_d': ArrayD
|
|
1914
|
+
}.get(vizcol.get('aggregate'), ArrayD)
|
|
1915
|
+
# ...determine projection mode
|
|
1916
|
+
if vizcol.get('entity', True):
|
|
1917
|
+
# case: whole entities
|
|
1918
|
+
return aggregate(path.context).alias(alias)
|
|
1919
|
+
else:
|
|
1920
|
+
# case: specified attribute value(s)
|
|
1921
|
+
if outbound_only:
|
|
1922
|
+
# for outbound only paths, we can project a single value
|
|
1923
|
+
return path.context.columns[terminal].alias(alias)
|
|
1924
|
+
else:
|
|
1925
|
+
# otherwise, we need to use aggregate the values
|
|
1926
|
+
return aggregate(path.context.columns[terminal]).alias(alias)
|
|
1927
|
+
else:
|
|
1928
|
+
raise ValueError('Malformed source: %s' % str(source))
|
|
1929
|
+
else:
|
|
1930
|
+
raise ValueError('Malformed visible column: %s' % str(vizcol))
|
|
1931
|
+
|
|
1932
|
+
|
|
1933
|
+
def _datapath_contextualize(path, context_name='*', context_body=None, groupkey_name='RID'):
|
|
1934
|
+
"""Contextualizes a data path to a named visible columns context.
|
|
1935
|
+
|
|
1936
|
+
:param path: a datapath object
|
|
1937
|
+
:param context_name: name of the context within the path's terminating table's "visible columns" annotations
|
|
1938
|
+
:param context_body: a list of visible column definitions, if given, the `context_name` will be ignored
|
|
1939
|
+
:param groupkey_name: column name for the group by key of the generated query expression (default: 'RID')
|
|
1940
|
+
:return: a 'contextualized' attribute group query object
|
|
1941
|
+
"""
|
|
1942
|
+
assert isinstance(path, DataPath)
|
|
1943
|
+
path = copy.deepcopy(path)
|
|
1944
|
+
context = path.context
|
|
1945
|
+
table = context._wrapped_table
|
|
1946
|
+
sources = table.annotations.get(_erm.tag.source_definitions, {}).get('sources')
|
|
1947
|
+
vizcols = context_body if context_body else table.annotations.get(_erm.tag.visible_columns, {}).get(context_name, [])
|
|
1948
|
+
if not vizcols:
|
|
1949
|
+
raise ValueError('Visible columns context "%s" not found for table %s:%s' % (context_name, table.schema.name, table.name))
|
|
1950
|
+
groupkey = context.columns[groupkey_name]
|
|
1951
|
+
projection = []
|
|
1952
|
+
|
|
1953
|
+
for vizcol in vizcols:
|
|
1954
|
+
try:
|
|
1955
|
+
projection.append(_datapath_deserialize_vizcolumn(path, vizcol, sources=sources))
|
|
1956
|
+
path.context = context
|
|
1957
|
+
except ValueError as e:
|
|
1958
|
+
logger.warning(str(e))
|
|
1959
|
+
|
|
1960
|
+
def not_same_as_group_key(x):
|
|
1961
|
+
assert isinstance(groupkey, _ColumnWrapper)
|
|
1962
|
+
if not isinstance(x, _ColumnWrapper):
|
|
1963
|
+
return True
|
|
1964
|
+
return groupkey._wrapped_column != x._wrapped_column
|
|
1965
|
+
|
|
1966
|
+
projection = filter(not_same_as_group_key, projection) # project groupkey only once
|
|
1967
|
+
query = path.groupby(groupkey).attributes(*projection)
|
|
1968
|
+
return query
|
|
1969
|
+
|
|
1970
|
+
|
|
1971
|
+
def _datapath_generate_simple_denormalization(path, include_whole_entities=False):
|
|
1972
|
+
"""Generates a denormalized form of the table expressed in a visible columns specification.
|
|
1973
|
+
|
|
1974
|
+
:param path: a datapath object
|
|
1975
|
+
:param include_whole_entities: if a denormalization cannot find a 'name' like terminal, include the whole entity (i.e., all attributes), else return just the 'RID'
|
|
1976
|
+
:return: a generated visible columns specification based on a denormalization heuristic
|
|
1977
|
+
"""
|
|
1978
|
+
assert isinstance(path, DataPath)
|
|
1979
|
+
context = path.context
|
|
1980
|
+
table = context._wrapped_table
|
|
1981
|
+
|
|
1982
|
+
fkeys = list(table.foreign_keys)
|
|
1983
|
+
single_column_fkeys = {
|
|
1984
|
+
fkey.foreign_key_columns[0].name: fkey
|
|
1985
|
+
for fkey in table.foreign_keys if len(fkey.foreign_key_columns) == 1
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
def _fkey_to_vizcol(name, fk, inbound=None):
|
|
1989
|
+
# name columns to look for in related tables
|
|
1990
|
+
name_candidates = [
|
|
1991
|
+
'displayname',
|
|
1992
|
+
'preferredname',
|
|
1993
|
+
'fullname',
|
|
1994
|
+
'name',
|
|
1995
|
+
'title',
|
|
1996
|
+
'label'
|
|
1997
|
+
]
|
|
1998
|
+
|
|
1999
|
+
# determine terminal column
|
|
2000
|
+
terminal = 'RID'
|
|
2001
|
+
for candidate_col in fk.pk_table.columns:
|
|
2002
|
+
if candidate_col.name.lower().replace(' ', '').replace('_', '') in name_candidates:
|
|
2003
|
+
terminal = candidate_col.name
|
|
2004
|
+
break
|
|
2005
|
+
|
|
2006
|
+
# define source path
|
|
2007
|
+
source = [{'outbound': fk.names[0]}, terminal]
|
|
2008
|
+
if inbound:
|
|
2009
|
+
source = [{'inbound': inbound.names[0]}] + source
|
|
2010
|
+
|
|
2011
|
+
# return vizcol spec
|
|
2012
|
+
return {
|
|
2013
|
+
'markdown_name': name,
|
|
2014
|
+
'source': source,
|
|
2015
|
+
'entity': include_whole_entities and terminal == 'RID'
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
# assemble the visible column:
|
|
2019
|
+
# 1. column or single column fkeys
|
|
2020
|
+
# 2. all other (outbound fkey) related tables
|
|
2021
|
+
# 3. all associated tables
|
|
2022
|
+
vizcols = []
|
|
2023
|
+
for col in table.column_definitions:
|
|
2024
|
+
if col.name in single_column_fkeys:
|
|
2025
|
+
fkey = single_column_fkeys[col.name]
|
|
2026
|
+
vizcols.append(_fkey_to_vizcol(col.name, fkey))
|
|
2027
|
+
del single_column_fkeys[col.name]
|
|
2028
|
+
fkeys.remove(fkey)
|
|
2029
|
+
else:
|
|
2030
|
+
vizcols.append(col.name)
|
|
2031
|
+
|
|
2032
|
+
for outbound_fkey in fkeys:
|
|
2033
|
+
vizcols.append(_fkey_to_vizcol(outbound_fkey.constraint_name, outbound_fkey))
|
|
2034
|
+
|
|
2035
|
+
for inbound_fkey in table.referenced_by:
|
|
2036
|
+
if inbound_fkey.table.is_association():
|
|
2037
|
+
vizcols.append(
|
|
2038
|
+
_fkey_to_vizcol(
|
|
2039
|
+
inbound_fkey.table.name,
|
|
2040
|
+
inbound_fkey.table.foreign_keys[0] if inbound_fkey != inbound_fkey.table.foreign_keys[0] else inbound_fkey.table.foreign_keys[1],
|
|
2041
|
+
inbound=inbound_fkey
|
|
2042
|
+
)
|
|
2043
|
+
)
|
|
2044
|
+
|
|
2045
|
+
return vizcols
|
|
2046
|
+
|
|
2047
|
+
def simple_denormalization(path):
|
|
2048
|
+
"""A simple heuristic denormalization."""
|
|
2049
|
+
return _datapath_generate_simple_denormalization(path)
|
|
2050
|
+
|
|
2051
|
+
def simple_denormalization_with_whole_entities(path):
|
|
2052
|
+
"""A simple heuristic denormalization with related and associated entities."""
|
|
2053
|
+
return _datapath_generate_simple_denormalization(path, include_whole_entities=True)
|
|
2054
|
+
|
|
2055
|
+
def _datapath_denormalize(path, context_name=None, heuristic=None, groupkey_name='RID'):
|
|
2056
|
+
"""Denormalizes a path based on annotations or heuristics.
|
|
2057
|
+
|
|
2058
|
+
:param path: a DataPath object
|
|
2059
|
+
:param context_name: name of the visible-columns context or if none given, will attempt apply heuristics
|
|
2060
|
+
:param heuristic: heuristic to apply if no context name specified
|
|
2061
|
+
:param groupkey_name: column name for the group by key of the generated query expression (default: 'RID')
|
|
2062
|
+
"""
|
|
2063
|
+
assert isinstance(path, DataPath)
|
|
2064
|
+
assert context_name is None or isinstance(context_name, str)
|
|
2065
|
+
assert isinstance(groupkey_name, str)
|
|
2066
|
+
heuristic = heuristic or simple_denormalization
|
|
2067
|
+
assert callable(heuristic)
|
|
2068
|
+
return _datapath_contextualize(
|
|
2069
|
+
path,
|
|
2070
|
+
context_name=context_name,
|
|
2071
|
+
context_body=None if context_name else heuristic(path),
|
|
2072
|
+
groupkey_name=groupkey_name
|
|
2073
|
+
)
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from deriva.transfer.download.deriva_download import DerivaDownload, GenericDownloader, DerivaDownloadError, \
|
|
2
|
-
DerivaDownloadConfigurationError, DerivaDownloadAuthenticationError, DerivaDownloadAuthorizationError
|
|
2
|
+
DerivaDownloadConfigurationError, DerivaDownloadAuthenticationError, DerivaDownloadAuthorizationError, \
|
|
3
|
+
DerivaDownloadBaggingError
|
|
3
4
|
from deriva.transfer.download.deriva_download_cli import DerivaDownloadCLI
|
|
4
5
|
|
|
5
6
|
from deriva.transfer.upload.deriva_upload import DerivaUpload, GenericUploader, DerivaUploadError, DerivaUploadError, \
|
|
6
|
-
DerivaUploadConfigurationError, DerivaUploadCatalogCreateError, DerivaUploadCatalogUpdateError
|
|
7
|
+
DerivaUploadConfigurationError, DerivaUploadCatalogCreateError, DerivaUploadCatalogUpdateError, \
|
|
8
|
+
DerivaUploadAuthenticationError
|
|
7
9
|
from deriva.transfer.upload.deriva_upload_cli import DerivaUploadCLI
|
|
8
10
|
|
|
9
11
|
from deriva.transfer.backup.deriva_backup import DerivaBackup, DerivaBackupAuthenticationError, \
|