cloudcatalog 0.4__tar.gz → 0.5__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.
- cloudcatalog-0.5/.github/bug_report.md +38 -0
- cloudcatalog-0.5/.github/feature_request.md +20 -0
- {cloudcatalog-0.4/src/cloudcatalog.egg-info → cloudcatalog-0.5}/PKG-INFO +7 -8
- {cloudcatalog-0.4 → cloudcatalog-0.5}/README.md +3 -3
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/Notes.md +1 -1
- cloudcatalog-0.5/docs/cloudcatalog-spec-05.md +398 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/cloudcatalog_demo.py +0 -2
- {cloudcatalog-0.4 → cloudcatalog-0.5}/pyproject.toml +3 -3
- {cloudcatalog-0.4 → cloudcatalog-0.5/src/cloudcatalog.egg-info}/PKG-INFO +7 -8
- {cloudcatalog-0.4 → cloudcatalog-0.5}/src/cloudcatalog.egg-info/SOURCES.txt +6 -3
- cloudcatalog-0.5/src/cloudcatalog.egg-info/top_level.txt +5 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/src/cloudcatalog.py +150 -17
- cloudcatalog-0.5/src/quickfail.py +9 -0
- cloudcatalog-0.4/LICENSE +0 -21
- cloudcatalog-0.4/src/cloudcatalog.egg-info/top_level.txt +0 -4
- {cloudcatalog-0.4 → cloudcatalog-0.5}/.gitignore +0 -0
- /cloudcatalog-0.4/LICENSE.txt → /cloudcatalog-0.5/LICENSE.MD +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/TODO.md +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/Makefile +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/conf.py +0 -0
- {cloudcatalog-0.4/docs → cloudcatalog-0.5/docs/earlier}/cloudcatalog-spec-04.md +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/index.rst +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/make.bat +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/docs/scr_logo.png +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/setup.cfg +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/src/__init__.py +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/src/cloudcatalog.egg-info/dependency_links.txt +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/src/cloudcatalog.egg-info/requires.txt +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/src/data/README.rst +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/tests/__init__.py +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/tests/test_catalog_registry.py +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/tests/test_entire_catalog_search.py +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/tests/test_file_registry.py +0 -0
- {cloudcatalog-0.4 → cloudcatalog-0.5}/tests/validation.py +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Create a report to help us improve
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Describe the bug**
|
|
11
|
+
A clear and concise description of what the bug is.
|
|
12
|
+
|
|
13
|
+
**To Reproduce**
|
|
14
|
+
Steps to reproduce the behavior:
|
|
15
|
+
1. Go to '...'
|
|
16
|
+
2. Click on '....'
|
|
17
|
+
3. Scroll down to '....'
|
|
18
|
+
4. See error
|
|
19
|
+
|
|
20
|
+
**Expected behavior**
|
|
21
|
+
A clear and concise description of what you expected to happen.
|
|
22
|
+
|
|
23
|
+
**Screenshots**
|
|
24
|
+
If applicable, add screenshots to help explain your problem.
|
|
25
|
+
|
|
26
|
+
**Error Log**
|
|
27
|
+
If applicable, add the log dump produced by the bug.
|
|
28
|
+
|
|
29
|
+
**Desktop (please complete the following information):**
|
|
30
|
+
- OS: [e.g. iOS]
|
|
31
|
+
- Browser [e.g. chrome, safari] (if applicable)
|
|
32
|
+
- Platform version [e.g. 1.0.0]
|
|
33
|
+
- Python version
|
|
34
|
+
- AWS CLI version
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
**Additional context**
|
|
38
|
+
Add any other context about the problem here.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest an idea for this project
|
|
4
|
+
title: ''
|
|
5
|
+
labels: ''
|
|
6
|
+
assignees: ''
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**Is your feature request related to a problem? Please describe.**
|
|
11
|
+
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
|
12
|
+
|
|
13
|
+
**Describe the solution you'd like**
|
|
14
|
+
A clear and concise description of what you want to happen.
|
|
15
|
+
|
|
16
|
+
**Describe alternatives you've considered**
|
|
17
|
+
A clear and concise description of any alternative solutions or features you've considered.
|
|
18
|
+
|
|
19
|
+
**Additional context**
|
|
20
|
+
Add any other context or screenshots about the feature request here.
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloudcatalog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: API for accessing the generalized Cloud Catalog (cloudcatalog) specification for sharing data in and across clouds
|
|
5
5
|
Author-email: Johns Hopkins University Applied Physics Laboratory LLC <sandy.antunes@jhuapl.edu>
|
|
6
6
|
License: MIT License
|
|
7
7
|
Project-URL: Homepage, https://heliocloud.org
|
|
8
|
-
Project-URL: Documentation, https://heliocloud
|
|
9
|
-
Project-URL: Repository, https://
|
|
8
|
+
Project-URL: Documentation, https://github.com/heliocloud-data/cloudcatalog
|
|
9
|
+
Project-URL: Repository, https://github.com/heliocloud-data/cloudcatalog
|
|
10
10
|
Keywords: cloud,index,catalog,AWS
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Topic :: Utilities
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Requires-Python: >=3.8
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
License-File: LICENSE.txt
|
|
16
|
+
License-File: LICENSE.MD
|
|
18
17
|
Requires-Dist: boto3
|
|
19
18
|
Requires-Dist: pandas
|
|
20
19
|
|
|
@@ -66,17 +65,17 @@ print(fr.get_entries())
|
|
|
66
65
|
# also save the downloaded file index
|
|
67
66
|
fr_id = 'a_dataset_id_from_the_catalog'
|
|
68
67
|
start_date = '2007-02-01T00:00:00Z' # A ISO 8601 standard time and a valid time witin the mission/file-index
|
|
69
|
-
|
|
68
|
+
stop_date = None # A ISO 8601 standard time or None if want all the file indices after start_date
|
|
70
69
|
myfiles = fr.request_cloud_catalog(fr_id, start_date=start_date, end_date=end_date, overwrite=False)
|
|
71
70
|
```
|
|
72
71
|
|
|
73
72
|
### Streaming Data from the File Catalog
|
|
74
|
-
You now have a pandas DataFrame with startdate, key, and filesize for all the files of the mission within your specified start and end dates. From here, you can use the key to stream some of the data through EC2, a Lambda, or other processing methods.
|
|
73
|
+
You now have a pandas DataFrame with startdate, stopdate, key, and filesize for all the files of the mission within your specified start and end dates. From here, you can use the key to stream some of the data through EC2, a Lambda, or other processing methods.
|
|
75
74
|
|
|
76
75
|
This tool also offers a simple function for streaming the data once the file catalog is obtained:
|
|
77
76
|
|
|
78
77
|
```python
|
|
79
|
-
cloudcatalog.CloudCatalog.stream(cloud_catalog, lambda bfile, startdate, filesize: print(len(bo.read()), filesize))
|
|
78
|
+
cloudcatalog.CloudCatalog.stream(cloud_catalog, lambda bfile, startdate, stopdate, filesize: print(len(bo.read()), filesize))
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
### Searching the Entire Catalog
|
|
@@ -46,17 +46,17 @@ print(fr.get_entries())
|
|
|
46
46
|
# also save the downloaded file index
|
|
47
47
|
fr_id = 'a_dataset_id_from_the_catalog'
|
|
48
48
|
start_date = '2007-02-01T00:00:00Z' # A ISO 8601 standard time and a valid time witin the mission/file-index
|
|
49
|
-
|
|
49
|
+
stop_date = None # A ISO 8601 standard time or None if want all the file indices after start_date
|
|
50
50
|
myfiles = fr.request_cloud_catalog(fr_id, start_date=start_date, end_date=end_date, overwrite=False)
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
### Streaming Data from the File Catalog
|
|
54
|
-
You now have a pandas DataFrame with startdate, key, and filesize for all the files of the mission within your specified start and end dates. From here, you can use the key to stream some of the data through EC2, a Lambda, or other processing methods.
|
|
54
|
+
You now have a pandas DataFrame with startdate, stopdate, key, and filesize for all the files of the mission within your specified start and end dates. From here, you can use the key to stream some of the data through EC2, a Lambda, or other processing methods.
|
|
55
55
|
|
|
56
56
|
This tool also offers a simple function for streaming the data once the file catalog is obtained:
|
|
57
57
|
|
|
58
58
|
```python
|
|
59
|
-
cloudcatalog.CloudCatalog.stream(cloud_catalog, lambda bfile, startdate, filesize: print(len(bo.read()), filesize))
|
|
59
|
+
cloudcatalog.CloudCatalog.stream(cloud_catalog, lambda bfile, startdate, stopdate, filesize: print(len(bo.read()), filesize))
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
### Searching the Entire Catalog
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
<!-- TOC -->
|
|
2
|
+
[1 Introduction](#1-intro)<br/>
|
|
3
|
+
[2 Global data registry](#2-dataregistry)<br/>
|
|
4
|
+
[3 Catalog](#3-catalog)<br/>
|
|
5
|
+
[4 File Indices](#4-fileindex)<br/>
|
|
6
|
+
[5 Info Metadata](#5-info)<br/>
|
|
7
|
+
<!-- \TOC -->
|
|
8
|
+
|
|
9
|
+
Version 0.5.0 \| HelioCloud \|
|
|
10
|
+
|
|
11
|
+
# 1 The generalized Cloud Catalog specification for HelioCloud
|
|
12
|
+
|
|
13
|
+
The shared Cloud Catalog specification can be used for sharing datasets across cloud frameworks.
|
|
14
|
+
|
|
15
|
+
For HelioCloud, this specification creates a global data registry ('HelioDataRegistry'), maintained at the HDRL GitLab repository, then defines the file catalogs ('cloudCatalog') for each dataset, that resides in the S3 (or equivalent) bucket alongside the dataset.
|
|
16
|
+
|
|
17
|
+
That 'HelioDataRegistry.json' is a JSON file consisting of **name** and **endpoint** and lists buckets as endpoints, not datasets. Tools can visit each **endpoint** to get the **catalog.json** listing datasets available in that bucket. The 'cloudCatalog' consists of the required index to the actual files, and an optional dataset summary file. The cloudCatalog itself is a set of ***<id>_YYYY.csv*** (or csp-zip or parquet) index files, one per year. The optional summary file is named **<id>.json** file and provides additional potentially searchable metadata.
|
|
18
|
+
|
|
19
|
+
## 1.1 Flow
|
|
20
|
+
|
|
21
|
+
Findability is through the 'HelioDataRegistry' JSON index of S3 buckets, which leads to the **catalog.json** for each S3 bucket that indicates available datasets, which points to the individual dataset file catalog index files **<id>_YYYY.csv** and its optional associated **<id>.info** metadata auxillary file.
|
|
22
|
+
|
|
23
|
+
(Diagram here) github.com/whatever/HelioDataRegistry.json -> s3://aplcloud.com/mybucket/catalog.json -> individual dataset file catalogs (.csv)
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
Datasets are findable by going to 'HelioDataRegistry.json' to get endpoints, then visiting each S3 endpoint to get the catalog listing of datasets available.
|
|
28
|
+
|
|
29
|
+
Accessing the actual data involves accessing each dataset's catalog index files. The cloudCatalog consists of CSV files, one per year. Therefore, a user has the choice to:
|
|
30
|
+
* (a) download the CSV file
|
|
31
|
+
* (b) use our provided Python API to fetch a subset of the CSV file, by date range
|
|
32
|
+
* (c) use AWS Athena to run queries on the CSV file
|
|
33
|
+
|
|
34
|
+
## 1.2 Uploading Data
|
|
35
|
+
|
|
36
|
+
Data providers must provide both the Data. the Dataset Description (in .json as per the catalog.json spec) and a Manifest (in the .csv format we specify).
|
|
37
|
+
|
|
38
|
+
We will generate an S3 Inventory of the contents and check against their Manifest, and send them the 'diff' file. It is their responsibility to provide us with a corrected Manifest. This iterates until their Manifest matches the actual Data provided.
|
|
39
|
+
|
|
40
|
+
## 1.3 Refreshing Data
|
|
41
|
+
|
|
42
|
+
Periodic updates to datasets go into an upload bucket. The process is generally the same as for Uploading Data: provide Data and Manifest. Once the Manifest matches the Data, we move the data from the upload bucket to the proper location and append the new Manifest to the existing Manifests.
|
|
43
|
+
|
|
44
|
+
## 1.4 Scope
|
|
45
|
+
|
|
46
|
+
We will produce Python tools that fit this API; a web form or similar tool for users to register datasets; automation to move datasets from an upload bucket to the data buckets; scripts to generate the AWS Inventory and compare against a user-provided Manifest; a sample use of Athena against the file catalogs for queries.
|
|
47
|
+
|
|
48
|
+
This meets requirements 1, 4, 5, 6, 7 and 11 from the user stories/requirements document,, and opens up 9 (current scope is data be findable, whereas item 9 adds searchability).
|
|
49
|
+
|
|
50
|
+
# 2. Global data registry 'HelioDataRegistry.json'
|
|
51
|
+
|
|
52
|
+
The global data registry is an unsorted json list of names and bucket endpoints. This is for buckets, not datasets (a bucket can hold multiple datasets). Queries to each endpoint can fetch the list off datasets and more information from the **endpoint**/catalog.json file.
|
|
53
|
+
|
|
54
|
+
The definitive listing of available dataset is kept in the **name for GSFC HelioCloud registry**. Providers who wish to make data visibile to the public do a one-time registration of their S3 bucket (or equivalent) to the main registry
|
|
55
|
+
The global data registry can be accessed directly or mirrored by anyone.
|
|
56
|
+
|
|
57
|
+
Once an S3 bucket (or equivalent) is registered in the HelioDataRegistry.json file, it does not have to be updated when new datasets are added, only when new buckets are made public.
|
|
58
|
+
|
|
59
|
+
For this global registry, only S3 buckets, not subbuckets, are allowed. For example, 's3://helio-public/' is allowed, but 's3://helio-public/MMS/' is not.
|
|
60
|
+
|
|
61
|
+
## 2.1 Global Data Registry specification
|
|
62
|
+
|
|
63
|
+
The registry is a JSON file containing the version of this specification, the date it was last modified, and the registry list of datasets. Each dataset has two items: **endpoint** and **name**. **endpoint**s must be unique; names do not enforce uniqueness.
|
|
64
|
+
|
|
65
|
+
* **endpoint** - An accessble S3 (or equivalent) bucket link
|
|
66
|
+
* **name** - A descriptive name for the dataset.
|
|
67
|
+
* **provider** - default 'aws', indicates which cloud provider. Included for future federation.
|
|
68
|
+
* **region** - The AWS or equivalent region the bucket resides in
|
|
69
|
+
|
|
70
|
+
The **name** should be brief and one-line; length is not enforced by the specification but may be truncated when registration to keep things readable.
|
|
71
|
+
|
|
72
|
+
## 2.2 Sample global data registry
|
|
73
|
+
|
|
74
|
+
Here is a sample 'HelioDataRegistry.json' for three buckets at two sites.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
{
|
|
78
|
+
"version": "0.3",
|
|
79
|
+
"modificationDate": "2022-01-01T00:00.00Z",
|
|
80
|
+
"registry": [
|
|
81
|
+
{
|
|
82
|
+
"endpoint": "s3://gov-nasa-hdrl-data1/",
|
|
83
|
+
"name": "GSFC HelioCloud Set 1",
|
|
84
|
+
"provider": "aws",
|
|
85
|
+
"region": "us-east-1"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"endpoint": "s3://gov-nasa-hdrl-data2/",
|
|
89
|
+
"name": "GSFC HelioCloud Set 2",
|
|
90
|
+
"provider": "aws",
|
|
91
|
+
"region": "us-east-1"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"endpoint": "s3://edu-apl-helio-public/",
|
|
95
|
+
"name": "APL HelioCLoud",
|
|
96
|
+
"provider": "aws",
|
|
97
|
+
"region": "us-west-1"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
# 3. Catalog of File Registries (per bucket catalog.json)
|
|
104
|
+
|
|
105
|
+
The catalog.json file has an entry for each dataset stored within the given S3 bucket. **endpoint** and **name** are identical to the item in the global data registry.
|
|
106
|
+
|
|
107
|
+
Globally the catalog.json describes the endpoint with the following items. Note that **endpoint** and **name** are included for clarity sake (being duplicates of the main registry) as JSON does not allow for comments, and should be the same as was provided to the global registry.
|
|
108
|
+
|
|
109
|
+
* **endpoint** - same as was provided to GlobalDataRegistry.json, an accessble S3 (or equivalent) bucket link
|
|
110
|
+
* **name** - same as was provided to GlobalDataRegistry.json, a descriptive name for the dataset.
|
|
111
|
+
* **provider** - same as was provided to GlobalDataRegistry.json, default 'aws', indicates which cloud provider. Included for future federation.
|
|
112
|
+
* **region** - same as was provided to GlobalDataRegistry.json, which AWS region
|
|
113
|
+
* **egress** - one of 'no-egress', 'user-pays', 'egress-allowed', or 'none'
|
|
114
|
+
* **status** - A return code, typically "1200/OK". Site owners can temporarily set this to other values
|
|
115
|
+
* **contact** - Who to contact for issues with this bucket, e.g. "Dr. Contact, dr_contact@example.com"
|
|
116
|
+
|
|
117
|
+
* **description** - Optional description of this collection
|
|
118
|
+
* **citation** - Optional how to cite, preferably a DOI for the server
|
|
119
|
+
* **comment** - A catch-all comment field for data provider and developer use. It should not contain information required to parse the data items.
|
|
120
|
+
|
|
121
|
+
For each dataset, the catalog entry requires:
|
|
122
|
+
|
|
123
|
+
* **id** a unique ID for the dataset that follows the ID naming requirements
|
|
124
|
+
* **index** a fully qualified pointer to the object directory containing both the dataset and the required cloudCatalog. It MUST start with s3:// or "https://" (or equivalent) end in a terminating '/'.
|
|
125
|
+
* **start**: string, Restricted ISO 8601 date/time of first record of data in the entire dataset OR the word 'static' for items such as model shapes that lack a time field.
|
|
126
|
+
* **stop**: string, Restricted ISO 8601 date/time of end of the last
|
|
127
|
+
record of data in the entire dataset OR the word 'static' for items such as model shapes that lack a time field.
|
|
128
|
+
* **modification**: string, Restricted ISO 8601 date/time of last time this dataset was updated
|
|
129
|
+
* **title** a short descriptive title sufficient to identify the dataset and its utility to users
|
|
130
|
+
* **indextype** Defines what format the actual cloudCatalog is, one of 'csv', 'csv-zip' or 'parquet'
|
|
131
|
+
* **filetype** the file format of the actual data. Must be from the prescribed list of files.
|
|
132
|
+
|
|
133
|
+
* **description** optional description for dataset".
|
|
134
|
+
* **resource** optional identifier e.g. SPASE ID, dataset description URL, DOI, json link of model parameters, or similar ancillary information".
|
|
135
|
+
* **creation** optional ISO 8601 date/time of the dataset creation".
|
|
136
|
+
* **expiration** optional ISO 8601 date/time after which the dataset will be expired, migrated, or not maintained".
|
|
137
|
+
* **verified** optional ISO 8601 date/time for when the dataset was last tested by verifier programs".
|
|
138
|
+
* **citation** optional how to cite this dataset, DOI or similar".
|
|
139
|
+
* **contact** optional contact name".
|
|
140
|
+
* **about** optional website URL for info, team, etc.
|
|
141
|
+
* **multiyear** optional True/False field (default: False) for use when dataitems span multiple years (see 3.2 below)
|
|
142
|
+
|
|
143
|
+
For file formats, there is a prescibed list. As new file formats are introduced, we will update this specification to give a single unique identifier for it. The reason for the prescribed list is to avoid ambiguity or the need for users to parse (for example, avoiding figuring out '.fts', 'fits', '.FTS', etc) Repositories with multiple files types can specify them as a comma-separated list with no spaces, e.g. 'fits,csv' for a dataset that contains both images and event lists. Currently defined types are 'fits,csv,cdf,netcdf3,netcdf4,hdf5,datamap,txt,binary,other'.
|
|
144
|
+
|
|
145
|
+
The catalog.json file has to be updated when new data is added to a dataset, by updating the **stop** item. Also, the catalog.json file is updated when a new dataset is put into that S3 bucket.
|
|
146
|
+
|
|
147
|
+
Note, currently this specification is defined around "s3://" architecture with some support for "https://" endpoints; future versions may support other protocols.
|
|
148
|
+
|
|
149
|
+
## 3.1 ID naming requirements
|
|
150
|
+
|
|
151
|
+
The **id** field can only contain alphanumeric characters, dashes, or underscores. No spaces or other characters are allowed.
|
|
152
|
+
|
|
153
|
+
The **id** field will match the cloudCatalog files but does not have to match the sub-bucket names. The cloudCatalog includes the **id**_YYYY.csv file indices and the optional **id**.json metadata file.
|
|
154
|
+
|
|
155
|
+
## 3.2 Data items spanning multiple years
|
|
156
|
+
|
|
157
|
+
Some data or model outputs span multiple years. An output product that covers 3 years, for example (2011-2013) would be index in "id"_2011.csv (based on the start date of the data). A time-based search that is looking for 2012 coverage would therefore not be able to find it, as no "id"_2012.csv file exists or is needed. To accommodate these edge cases, the **multiyear** field should be set to True, which will allow subsequent search layers to be aware that long-duration files exist in this dataset.
|
|
158
|
+
|
|
159
|
+
This field should not be set for the typical case where a datafile extends slightly into the next year, but only when a datafile exists to provide data for a calendar year and that datafile would not be findable based purely on its given start date.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## 3.2 Status codes
|
|
163
|
+
|
|
164
|
+
The default status code for a catalog.json item is "1200/OK" indicating the data is available. Status codes are informative and do not enforce any limits. They exist to communicate to users and client programs if a dataset is temporarily down or has other constraints.
|
|
165
|
+
Other status codes defined so far include:
|
|
166
|
+
* "code": "1200", "message": "OK": system is up and running
|
|
167
|
+
* "code": "1400", "message": "temporarily unavailable": providers can set this if they temporarily are doing maintenance or need to stop access due to costs
|
|
168
|
+
|
|
169
|
+
## 3.3 Example
|
|
170
|
+
|
|
171
|
+
Here is an example catalog, for which only the first item has decided to fill out the optional 'ownership' block. If this was the GSFC catalog.json, it would reside at s3://gov-nasa-helio-public/catalog.json.
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
{
|
|
175
|
+
"version": "0.3",
|
|
176
|
+
"endpoint": "s3://gov-nasa-helio-public/",
|
|
177
|
+
"name": "GSFC HelioCloud",
|
|
178
|
+
"provider": "aws",
|
|
179
|
+
"region": "us-east-1",
|
|
180
|
+
"egress": "no-egress",
|
|
181
|
+
"contact": "Dr. Contact, dr_contact@example.com",
|
|
182
|
+
"description": "Optional description of this collection",
|
|
183
|
+
"citation": "Optional how to cite, preferably a DOI for the server",
|
|
184
|
+
"catalog":[
|
|
185
|
+
{
|
|
186
|
+
"id": "euvml",
|
|
187
|
+
"index": "s3://gov-nasa-helio-public/euvml/",
|
|
188
|
+
"title": "EUV-ML dataset",
|
|
189
|
+
"start": "1995-01-01T00:00.00Z",
|
|
190
|
+
"stop": "2022-01-01T00:00.00Z",
|
|
191
|
+
"modification": "2022-01-01T00:00.00Z",
|
|
192
|
+
"indextype": "csv",
|
|
193
|
+
"filetype": "fits",
|
|
194
|
+
"description": "Optional description for dataset",
|
|
195
|
+
"resource": "optional SPASE ID, DOI, URL, or json modelset",
|
|
196
|
+
"creation": "optional ISO 8601 date/time of the dataset creation",
|
|
197
|
+
"citation": "optional how to cite this dataset, DOI or similar",
|
|
198
|
+
"contact": "optional contact name",
|
|
199
|
+
"about": "optional website URL for info, team, etc"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
"id": "mms_hmi",
|
|
203
|
+
"index": "s3://gov-nasa-helio-public/mms/hmi/",
|
|
204
|
+
"title": "MMS HMI data"
|
|
205
|
+
"start": "2015-01-01T00:00.00Z",
|
|
206
|
+
"stop": "2022-01-01T00:00.00Z",
|
|
207
|
+
"modification": "2022-01-01T00:00.00Z",
|
|
208
|
+
"indextype": "csv-zip",
|
|
209
|
+
"filetype": "cdf"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"id": "mms_feeps",
|
|
213
|
+
"index": "s3://gov-nasa-helio-public/mms/feeps/",
|
|
214
|
+
"title": "MMS FEEPS data"
|
|
215
|
+
"start": "2015-01-01T00:00.00Z",
|
|
216
|
+
"stop": "2022-01-01T00:00.00Z",
|
|
217
|
+
"modification": "2022-01-01T00:00.00Z",
|
|
218
|
+
"indextype": "csv-zip",
|
|
219
|
+
"filetype": "cdf"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"id": "fluxrope",
|
|
223
|
+
"index": "s3://heliotest/models/",
|
|
224
|
+
"title": "Instatiation of 3D fluxropes"
|
|
225
|
+
"start": "static",
|
|
226
|
+
"stop": "static",
|
|
227
|
+
"modification": "2022-01-01T00:00.00Z",
|
|
228
|
+
"indextype": "csv-zip",
|
|
229
|
+
"filetype": "cdf"
|
|
230
|
+
}
|
|
231
|
+
],
|
|
232
|
+
"status": {
|
|
233
|
+
"code": 1200,
|
|
234
|
+
"message": "OK request successful"
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## 3.4 Indexes should reside in same bucket as data
|
|
240
|
+
|
|
241
|
+
Index CSV files must be in the same bucket as the data, but do not have to be in the same sub-bucket or directory as their data. The catalog points to the index files, and the index files use absolute paths to point to the data items.
|
|
242
|
+
|
|
243
|
+
This also enables design a collection of datasets, wherein the data in the actual file catalog <ID>_<YYYY>.csv files can span sub-buckets. The use of absolute file paths is mandated.
|
|
244
|
+
|
|
245
|
+
```
|
|
246
|
+
Example data itself is in:
|
|
247
|
+
s3://example/mms1/feeps/
|
|
248
|
+
s3://example/mms2/feeps/
|
|
249
|
+
s3://example/mms3/feeps/
|
|
250
|
+
s3://example/mms4/feeps/
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Case 1: Matching
|
|
255
|
+
file catalog locations:
|
|
256
|
+
s3://example/mms1/feeps/mms1_feeps.CSV
|
|
257
|
+
s3://example/mms2/feeps/mms2_feeps.CSV
|
|
258
|
+
s3://example/mms3/feeps/mms3_feeps.CSV
|
|
259
|
+
s3://example/mms4/feeps/mms4_feeps.CSV
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
Case 2: Not Matching
|
|
264
|
+
file catalog locations:
|
|
265
|
+
s3://example/mms_all/feeps/mms_feeps.CSV (contents point to 4 subbuckets)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
The first case matches the idea of a 'dataset' and MUST be provided for any provided dataset. The second matches the idea of a 'collection' and is supported but not required (i.e. additional extra cloudCatalog endpoints do not have to match the underlying data structure).
|
|
269
|
+
|
|
270
|
+
## 3.5 Concerns
|
|
271
|
+
|
|
272
|
+
Concerns were voiced about clashes if multiple people attempt to edit the catalog.json for a bucket at the same time. Our default HelioCloud will maintain the catalog.json contents in a serverless DynamoDB (which will handle transaction collisions and provide rollback), which then outputs the 'catalog.json' file that users and data requests use.
|
|
273
|
+
|
|
274
|
+
# 4 File Catalog
|
|
275
|
+
|
|
276
|
+
The file catalog consists of one index for each year of the dataset in either csv, zipped csv, or parquet format. The file must be in time sequence and the first four items must be the **start**, **stop**, **datakey**, and **filesize** fields in that order.
|
|
277
|
+
|
|
278
|
+
The index cloudCatalog is a set of CSV or Parquet files named "index"/"id"_YYYY.csv, "index"/"id"_YYYY.csv.zip or "index"/"id"_YYYY.parquet. For the case of static non-time sequence outputs, the index cloudCatalog are named "index"/"id"_static.csv (or .csv.zip or .parquet).
|
|
279
|
+
|
|
280
|
+
## 4.1 Required Items
|
|
281
|
+
|
|
282
|
+
* **start**: string, Restricted ISO 8601 date/time of start for that data OR the word 'static' for items such as model shapes that lack a time field
|
|
283
|
+
* **stop**: string, Restricted ISO 8601 date/time of stop for that data OR the word 'static' for items such as model shapes that lack a time field
|
|
284
|
+
* **datakey**: string, full filename or S3 object identifier sufficient to actually obtain the file
|
|
285
|
+
* **filesize**: integer, file size in bytes
|
|
286
|
+
|
|
287
|
+
## 4.2 Optional Items
|
|
288
|
+
|
|
289
|
+
* **checksum**: checksum for that file. If given, **checksum_algorithm** must also be listed
|
|
290
|
+
* **checksum_algorithm**: checksum algorithm used if checksums are generated. Examples include SHA, others.
|
|
291
|
+
|
|
292
|
+
## 4.3 Optional Parameters
|
|
293
|
+
|
|
294
|
+
The default expected search capability is _filetime_ within requested time range.
|
|
295
|
+
|
|
296
|
+
Anything past **filesize** is fully optional; the minimal API expects only a start time, datakey aka filehandle, and file size IN THAT ORDER in the actual file index. It is up to individual client programs to do anything past that.
|
|
297
|
+
|
|
298
|
+
Any metadata included in this per-file index must be defined in the <id>.info json file to allow parseability.
|
|
299
|
+
|
|
300
|
+
The csv or zipped csv files may or may not include a one-line header that is prefaced by "#". It is the responsibility of client programs to determine if there is a skippable header or not.
|
|
301
|
+
|
|
302
|
+
Any optional parameters from the above list or the info JSON must be in the same order specified in the JSON.
|
|
303
|
+
|
|
304
|
+
For 'static' items, since the 'start' field will be the same constant value 'static' for all items, optional parameters to distinguish the items are suggested.
|
|
305
|
+
|
|
306
|
+
## 4.4 Accessing
|
|
307
|
+
|
|
308
|
+
Users have 3 options with the cloudCatalog CSV file:
|
|
309
|
+
* download it directly and parse yourself,
|
|
310
|
+
* use our Python API (provided) to extract a subset of filehandles from CSV,
|
|
311
|
+
* use AWS Athena for queries
|
|
312
|
+
|
|
313
|
+
## 4.5 Justification for Yearly CSV/Parquet Files
|
|
314
|
+
|
|
315
|
+
Unlike a database, yearly index files are both fetchable and parseable. They have a lower cost profile than the equivalent database, reasonably fast access, and allow for client and search programs independent of a specific database implementation.
|
|
316
|
+
|
|
317
|
+
Using yearly files rather than a single file or a database is to maintain an inexpensive, stateless, easily updated file index. In AWS, the "S3 Inventory" command can generate a list of filenames and datakeys, or filenames and datakeys since the last time inventory was run. Being able to add to the catalog index files incrementally is easier served if they are chunked into yearly files.
|
|
318
|
+
|
|
319
|
+
In addition, many use cases for long time baseline datasets will not need to access the entire multi-decadal span of the data, so parsing into years reduces the downloads needed to obtain the indices.
|
|
320
|
+
|
|
321
|
+
The use of CSV or Parquet also enables AWS Athena searches within the index with little overhead, so long as optional metadata is provided.
|
|
322
|
+
|
|
323
|
+
## 4.6 Example File Catalog
|
|
324
|
+
|
|
325
|
+
Here is a short minimal CSV example index file.
|
|
326
|
+
|
|
327
|
+
```
|
|
328
|
+
# start, stop, datakey, filesize
|
|
329
|
+
'2010-05-08T12:05:30.000Z','2010-05-08T12:06:14.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_120530_n4euA.fts','246000'
|
|
330
|
+
'2010-05-08T12:06:15.000Z','2010-05-08T12:10:29.00Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_120615_n4euA.fts','246000'
|
|
331
|
+
'2010-05-08T12:10:30.000Z','2010-05-08T12:14:29.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_121030_n4euA.fts','246000'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Here is an example with additional metadata and a CSV header as well.
|
|
335
|
+
|
|
336
|
+
```
|
|
337
|
+
# start, stop, datakey, filesize, wavelength, carr_lon, carr_lat
|
|
338
|
+
'2010-05-08T12:05:30.000Z','2010-05-08T12:06:14.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_120530_n4euA.fts','246000','195','20.4','30.0'
|
|
339
|
+
'2010-05-08T12:06:15.000Z','2010-05-08T12:10:29.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_120615_n4euA.fts','246000','195','21.8','30.0'
|
|
340
|
+
'2010-05-08T12:10:30.000Z','2010-05-08T12:14:29.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_121030_n4euA.fts','246000','195','22.4','30.0'
|
|
341
|
+
```
|
|
342
|
+
Here is an example with additional metadata and a CSV header as the EUV-ML project would like. Items in CAPS are directly from FITS keywords.
|
|
343
|
+
```
|
|
344
|
+
# start, stop, datakey, filesize, spacecraft, instrument, WAVELNTH, CRLT_OBS, CRLN_OBS, CRPIX1, CRPIX2, RSUN, quality, generation_flag
|
|
345
|
+
'2010-05-08T12:05:30.000Z','2010-05-08T12:06:14.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_120530_n4euA.fts','246000','A','euvi','195,45.0, 23.1, 512, 510, 26.5, 1, 1
|
|
346
|
+
'2010-05-08T12:06:15.000Z','2010-05-08T12:10:29.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_120615_n4euA.fts','246000','A','euvi','195',45.0, 23.1, 512, 510, 26.5, 1, 1
|
|
347
|
+
'2010-05-08T12:10:30.000Z','2010-05-08T12:14:29.000Z','s3://edu-apl-helio-public/euvml/stereo/a/195/20100508_121030_n4euA.fts','246000','A','euvi','195',45.0, 23.1, 512, 510, 26.5, 1, 1
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
# 5 Time Specification and ISO 8601
|
|
351
|
+
|
|
352
|
+
Time values are always strings, and the SCR Time format (taken from the HAPI Time format) is a subset of the ISO 8601 standard. The restriction on the ISO 8601 standard is that time must be represented as
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
yyyy-mm-ddThh:mm:ss.sssZ
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
and the trailing Z is required. Strings with less precision are allowed as per ISO 8601. Any date or time elements missing from the string are assumed to take on their smallest possible value. For example, the string 2017-01-15T23:00:00.000Z could be given in truncated form as 2017-01-15T23:00Z. A dataset must use only one format and length within that given dataset. The times values must not have any local time zone offset, and they must indicate this by including the trailing Z.
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
# 6 Info Metadata
|
|
362
|
+
|
|
363
|
+
Each dataset may also include an optional info json file that gives the time range, date last modified, ownership information, and optional additional metadata for that dataset. The files are by default searchable and selectable by time window. Additional search capability is not within scope of the file catalog per se, but data providers can indicate metadata for adding a search layer.
|
|
364
|
+
|
|
365
|
+
If an <id>.info file exists and lists additional parameters, the resulting catalog index files must contain those parameters in the same order as expressed in this json file.
|
|
366
|
+
|
|
367
|
+
## 6.1 Optional Items
|
|
368
|
+
|
|
369
|
+
* **parameters**: optional list of searchable parameters available in the actual catalog index files
|
|
370
|
+
file.
|
|
371
|
+
(* = available from S3 Inventory)
|
|
372
|
+
|
|
373
|
+
## 6.2 Example
|
|
374
|
+
|
|
375
|
+
Here is an example for a sample optional Info json file. This is used to indicate additional searchable metadata that exists within the catalog index files, and enables higher searchability in datasets.
|
|
376
|
+
|
|
377
|
+
```javascript
|
|
378
|
+
{
|
|
379
|
+
"version": "0.3",
|
|
380
|
+
"parameters": [
|
|
381
|
+
{"name": "spacecraft", "type": "string"},
|
|
382
|
+
{"name": "wavelength", "type": "int", "units": "Angstroms"},
|
|
383
|
+
{"name": "crlt", "type": "double", "units": "degrees", "desc": "Carrington latitude"},
|
|
384
|
+
{"name": "crln", "type": "double", "units": "degrees", "desc": "Carrington longitude"},
|
|
385
|
+
{"name": "rsun", "type": "double", "units": "pixels", "desc": "Size of sun in pizels"},
|
|
386
|
+
{"name": "crpix1", "type": "integer", "units": "pixels", "desc": "x coord of sun center"},
|
|
387
|
+
{"name": "crpix2", "type": "integer", "units": "pixels", "desc": "x coord of sun center"},
|
|
388
|
+
{"name": "quality", "type": "integer", "desc": "data quality and level of interpolation"}
|
|
389
|
+
]
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
## 7.0 Changes from 0.4 to 0.5
|
|
394
|
+
|
|
395
|
+
Changed
|
|
396
|
+
```
|
|
397
|
+
Added 'stop' as a mandatory field
|
|
398
|
+
```
|
|
@@ -6,7 +6,7 @@ build-backend = 'setuptools.build_meta'
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "cloudcatalog"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.5"
|
|
10
10
|
authors = [ {name = "Johns Hopkins University Applied Physics Laboratory LLC", email = "sandy.antunes@jhuapl.edu"}]
|
|
11
11
|
license = {text = "MIT License"}
|
|
12
12
|
description = "API for accessing the generalized Cloud Catalog (cloudcatalog) specification for sharing data in and across clouds"
|
|
@@ -25,5 +25,5 @@ dependencies = [
|
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
|
27
27
|
Homepage = "https://heliocloud.org"
|
|
28
|
-
Documentation = "https://heliocloud
|
|
29
|
-
Repository = "https://
|
|
28
|
+
Documentation = "https://github.com/heliocloud-data/cloudcatalog"
|
|
29
|
+
Repository = "https://github.com/heliocloud-data/cloudcatalog"
|
|
@@ -1,20 +1,19 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: cloudcatalog
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5
|
|
4
4
|
Summary: API for accessing the generalized Cloud Catalog (cloudcatalog) specification for sharing data in and across clouds
|
|
5
5
|
Author-email: Johns Hopkins University Applied Physics Laboratory LLC <sandy.antunes@jhuapl.edu>
|
|
6
6
|
License: MIT License
|
|
7
7
|
Project-URL: Homepage, https://heliocloud.org
|
|
8
|
-
Project-URL: Documentation, https://heliocloud
|
|
9
|
-
Project-URL: Repository, https://
|
|
8
|
+
Project-URL: Documentation, https://github.com/heliocloud-data/cloudcatalog
|
|
9
|
+
Project-URL: Repository, https://github.com/heliocloud-data/cloudcatalog
|
|
10
10
|
Keywords: cloud,index,catalog,AWS
|
|
11
11
|
Classifier: Development Status :: 4 - Beta
|
|
12
12
|
Classifier: Topic :: Utilities
|
|
13
13
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
14
|
Requires-Python: >=3.8
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
License-File: LICENSE.txt
|
|
16
|
+
License-File: LICENSE.MD
|
|
18
17
|
Requires-Dist: boto3
|
|
19
18
|
Requires-Dist: pandas
|
|
20
19
|
|
|
@@ -66,17 +65,17 @@ print(fr.get_entries())
|
|
|
66
65
|
# also save the downloaded file index
|
|
67
66
|
fr_id = 'a_dataset_id_from_the_catalog'
|
|
68
67
|
start_date = '2007-02-01T00:00:00Z' # A ISO 8601 standard time and a valid time witin the mission/file-index
|
|
69
|
-
|
|
68
|
+
stop_date = None # A ISO 8601 standard time or None if want all the file indices after start_date
|
|
70
69
|
myfiles = fr.request_cloud_catalog(fr_id, start_date=start_date, end_date=end_date, overwrite=False)
|
|
71
70
|
```
|
|
72
71
|
|
|
73
72
|
### Streaming Data from the File Catalog
|
|
74
|
-
You now have a pandas DataFrame with startdate, key, and filesize for all the files of the mission within your specified start and end dates. From here, you can use the key to stream some of the data through EC2, a Lambda, or other processing methods.
|
|
73
|
+
You now have a pandas DataFrame with startdate, stopdate, key, and filesize for all the files of the mission within your specified start and end dates. From here, you can use the key to stream some of the data through EC2, a Lambda, or other processing methods.
|
|
75
74
|
|
|
76
75
|
This tool also offers a simple function for streaming the data once the file catalog is obtained:
|
|
77
76
|
|
|
78
77
|
```python
|
|
79
|
-
cloudcatalog.CloudCatalog.stream(cloud_catalog, lambda bfile, startdate, filesize: print(len(bo.read()), filesize))
|
|
78
|
+
cloudcatalog.CloudCatalog.stream(cloud_catalog, lambda bfile, startdate, stopdate, filesize: print(len(bo.read()), filesize))
|
|
80
79
|
```
|
|
81
80
|
|
|
82
81
|
### Searching the Entire Catalog
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
.gitignore
|
|
2
|
-
LICENSE
|
|
3
|
-
LICENSE.txt
|
|
2
|
+
LICENSE.MD
|
|
4
3
|
README.md
|
|
5
4
|
TODO.md
|
|
6
5
|
pyproject.toml
|
|
6
|
+
.github/bug_report.md
|
|
7
|
+
.github/feature_request.md
|
|
7
8
|
docs/Makefile
|
|
8
9
|
docs/Notes.md
|
|
9
|
-
docs/cloudcatalog-spec-
|
|
10
|
+
docs/cloudcatalog-spec-05.md
|
|
10
11
|
docs/cloudcatalog_demo.py
|
|
11
12
|
docs/conf.py
|
|
12
13
|
docs/index.rst
|
|
13
14
|
docs/make.bat
|
|
14
15
|
docs/scr_logo.png
|
|
16
|
+
docs/earlier/cloudcatalog-spec-04.md
|
|
15
17
|
src/__init__.py
|
|
16
18
|
src/cloudcatalog.py
|
|
19
|
+
src/quickfail.py
|
|
17
20
|
src/cloudcatalog.egg-info/PKG-INFO
|
|
18
21
|
src/cloudcatalog.egg-info/SOURCES.txt
|
|
19
22
|
src/cloudcatalog.egg-info/dependency_links.txt
|
|
@@ -10,7 +10,110 @@ import logging
|
|
|
10
10
|
import dateutil
|
|
11
11
|
import re
|
|
12
12
|
from typing import List, Dict, Tuple, Union, Optional, Callable
|
|
13
|
+
from botocore import UNSIGNED
|
|
14
|
+
from botocore.client import Config
|
|
13
15
|
|
|
16
|
+
# Added handler that first tries S3 anonymous, then tries https/egreess
|
|
17
|
+
def s3url_to_https(s3url):
|
|
18
|
+
""" Formula is s3://BUCKET/KEY -> https://BUCKET.s3.amazonaws.com/KEY
|
|
19
|
+
"""
|
|
20
|
+
mybucket, mykey = s3url_to_bucketkey(s3url)
|
|
21
|
+
url = "https://" + mybucket + ".s3.amazonaws.com/" + mykey
|
|
22
|
+
return url
|
|
23
|
+
|
|
24
|
+
def s3url_to_bucketkey(s3url):
|
|
25
|
+
"""
|
|
26
|
+
Extracts the S3 bucket name and file key from an S3 URL.
|
|
27
|
+
|
|
28
|
+
S3 paths are weird, bucket + everything else, e.g.
|
|
29
|
+
s3://b1/b2/b3/t.txt would be bucket b1, file b2/b3/t.txt
|
|
30
|
+
|
|
31
|
+
:param s3url: The S3 URL to extract the bucket name and file key from.
|
|
32
|
+
|
|
33
|
+
:returns: A tuple containing the S3 bucket name and file key.
|
|
34
|
+
"""
|
|
35
|
+
# S3 paths are weird, bucket + everything else, e.g.
|
|
36
|
+
# s3://b1/b2/b3/t.txt would be bucket b1, file b2/b3/t.txt
|
|
37
|
+
name2 = re.sub(r"s3://", "", s3url)
|
|
38
|
+
s = name2.split("/", 1)
|
|
39
|
+
mybucket = s[0]
|
|
40
|
+
myfilekey = s[1] if len(s) > 1 else "" # Want None if no key?
|
|
41
|
+
return mybucket, myfilekey
|
|
42
|
+
|
|
43
|
+
def fetch_S3(s3url,unsigned=True,region=None,rawbytes=False, **client_kwargs):
|
|
44
|
+
# default is JSON, but can return raw bytes
|
|
45
|
+
#print("Trying S3, unsigned=",unsigned,"region=",region)
|
|
46
|
+
mybucket, mykey = s3url_to_bucketkey(s3url)
|
|
47
|
+
#print("Looking for: ",mybucket,mykey)
|
|
48
|
+
if unsigned:
|
|
49
|
+
if region != None:
|
|
50
|
+
s3_client = boto3.client("s3", config=Config(signature_version=UNSIGNED),region=region, **client_kwargs)
|
|
51
|
+
else:
|
|
52
|
+
s3_client = boto3.client("s3", config=Config(signature_version=UNSIGNED), **client_kwargs)
|
|
53
|
+
else:
|
|
54
|
+
if region != None:
|
|
55
|
+
s3_client = boto3.client("s3",region=region, **client_kwargs)
|
|
56
|
+
else:
|
|
57
|
+
s3_client = boto3.client("s3", **client_kwargs)
|
|
58
|
+
|
|
59
|
+
response = s3_client.get_object(Bucket=mybucket, Key=mykey)
|
|
60
|
+
status = response.get("ResponseMetadata", {}).get("HTTPStatusCode")
|
|
61
|
+
#print(" Success S3 unsigned",status)
|
|
62
|
+
if "Body" in response and status == 200:
|
|
63
|
+
catalog_bytes = response["Body"].read()
|
|
64
|
+
if rawbytes:
|
|
65
|
+
catalog = catalog_bytes
|
|
66
|
+
else:
|
|
67
|
+
catalog = json.loads(catalog_bytes)
|
|
68
|
+
elif status != 200:
|
|
69
|
+
print("Error, status = ",status)
|
|
70
|
+
return status, catalog
|
|
71
|
+
|
|
72
|
+
def fetch_url(s3url,rawbytes=False):
|
|
73
|
+
# default is JSON, but can return raw bytes
|
|
74
|
+
httpurl = s3url_to_https(s3url)
|
|
75
|
+
response = requests.get(httpurl)
|
|
76
|
+
status = response.status_code
|
|
77
|
+
if rawbytes:
|
|
78
|
+
catalog = response.content
|
|
79
|
+
else:
|
|
80
|
+
catalog = response.json()
|
|
81
|
+
return status, catalog
|
|
82
|
+
|
|
83
|
+
def fetch_S3orURL(s3url,region='us-east-1',rawbytes=False, **client_kwargs):
|
|
84
|
+
""" To get around vagualities of S3 access, this tries a cascade of:
|
|
85
|
+
straight fetch of S3 using your existing permissions
|
|
86
|
+
fetch S3 unsigned/anonymous
|
|
87
|
+
fetch S3 for a specified region only, defaulting to us-east-1
|
|
88
|
+
fetch the S3 contents via the AWS-equivalent URL
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
#print("Calling unsigned")
|
|
93
|
+
status, catalog = fetch_S3(s3url,unsigned=True,rawbytes=rawbytes, **client_kwargs)
|
|
94
|
+
except:
|
|
95
|
+
try:
|
|
96
|
+
#print("Calling signed")
|
|
97
|
+
status, catalog = fetch_S3(s3url,unsigned=False,rawbytes=rawbytes, **client_kwargs)
|
|
98
|
+
except:
|
|
99
|
+
try:
|
|
100
|
+
#print("Calling region")
|
|
101
|
+
status, catalog = fetch_S3(s3url, unsigned=True, region=region, rawbytes=rawbytes, **client_kwargs)
|
|
102
|
+
except:
|
|
103
|
+
try:
|
|
104
|
+
#print("Calling url")
|
|
105
|
+
status, catalog = fetch_url(s3url,rawbytes=rawbytes)
|
|
106
|
+
except:
|
|
107
|
+
#print("Cannot fetch catalog, exiting.")
|
|
108
|
+
return None
|
|
109
|
+
if rawbytes:
|
|
110
|
+
fr_bytes_file = BytesIO()
|
|
111
|
+
fr_bytes_file.write(catalog)
|
|
112
|
+
fr_bytes_file.seek(0)
|
|
113
|
+
return fr_bytes_file
|
|
114
|
+
else:
|
|
115
|
+
return catalog
|
|
116
|
+
|
|
14
117
|
|
|
15
118
|
class CatalogRegistry:
|
|
16
119
|
"""Use to work with the the global catalog (catalog of catalogs)."""
|
|
@@ -172,6 +275,8 @@ class CloudCatalog:
|
|
|
172
275
|
|
|
173
276
|
self.cache = cache
|
|
174
277
|
|
|
278
|
+
self.catalog = fetch_S3orURL(self.bucket_name+"/catalog.json",**client_kwargs)
|
|
279
|
+
""" # original version, added https mod
|
|
175
280
|
# Create a client object with provided kwargs
|
|
176
281
|
self.s3_client = boto3.client("s3", **client_kwargs)
|
|
177
282
|
|
|
@@ -189,7 +294,9 @@ class CloudCatalog:
|
|
|
189
294
|
|
|
190
295
|
# Load the content from json
|
|
191
296
|
self.catalog = json.loads(catalog_bytes)
|
|
192
|
-
|
|
297
|
+
"""
|
|
298
|
+
|
|
299
|
+
|
|
193
300
|
# Check catalog format assumptions
|
|
194
301
|
if any([key not in self.catalog for key in ["status", "catalog"]]):
|
|
195
302
|
raise KeyError(
|
|
@@ -231,8 +338,11 @@ class CloudCatalog:
|
|
|
231
338
|
os.mkdir(self.cache_folder)
|
|
232
339
|
|
|
233
340
|
# Copy the content of the catalog to this file (overwrites)
|
|
234
|
-
|
|
235
|
-
|
|
341
|
+
|
|
342
|
+
#with open(os.path.join(cache_folder, "catalog.json"), "wb") as file:
|
|
343
|
+
# file.write(catalog_bytes)
|
|
344
|
+
with open(os.path.join(cache_folder, "catalog.json"), "w") as file:
|
|
345
|
+
json.dump(self.catalog,file, indent=4, ensure_ascii=False)
|
|
236
346
|
|
|
237
347
|
def get_catalog(self) -> Dict:
|
|
238
348
|
"""
|
|
@@ -402,6 +512,9 @@ class CloudCatalog:
|
|
|
402
512
|
):
|
|
403
513
|
# May through some errors, NoSuchBucket, ClientError (file may not exists or access denied)
|
|
404
514
|
# If have ListBucket perms, no such key error will be raised instead of client error
|
|
515
|
+
|
|
516
|
+
fr_bytes_file = fetch_S3orURL(self.bucket_name+"/"+loc+filename,rawbytes=True)
|
|
517
|
+
""" # original
|
|
405
518
|
response = self.s3_client.get_object(Bucket=bucket_name, Key=loc + filename)
|
|
406
519
|
status = response.get("ResponseMetadata", {}).get("HTTPStatusCode")
|
|
407
520
|
if "Body" in response and status == 200:
|
|
@@ -412,7 +525,8 @@ class CloudCatalog:
|
|
|
412
525
|
raise FailedS3Get(
|
|
413
526
|
f"Failed to get a cloud catalog object. Status: {stats}. Response: {response}"
|
|
414
527
|
)
|
|
415
|
-
|
|
528
|
+
"""
|
|
529
|
+
|
|
416
530
|
if filepath is not None:
|
|
417
531
|
with open(filepath, "wb") as file:
|
|
418
532
|
file.write(fr_bytes_file.read())
|
|
@@ -423,6 +537,13 @@ class CloudCatalog:
|
|
|
423
537
|
else:
|
|
424
538
|
fr = pd.read_csv(filepath)
|
|
425
539
|
|
|
540
|
+
#print("Debug, version is ",self.catalog["Cloudy"])
|
|
541
|
+
if float(self.catalog["Cloudy"]) < 0.5:
|
|
542
|
+
# spec before 0.5 was start/key/filesize
|
|
543
|
+
# generate a 'maybe' stop using start time of prior entry
|
|
544
|
+
col0 = fr.columns[0]
|
|
545
|
+
fr.insert(1,"stop",fr[col0].shift(-1))
|
|
546
|
+
|
|
426
547
|
# Handle # if used for the header
|
|
427
548
|
if fr.columns.values[0][:2] == "# ":
|
|
428
549
|
fr.columns.values[0] = fr.columns.values[0][2:]
|
|
@@ -433,15 +554,17 @@ class CloudCatalog:
|
|
|
433
554
|
inplace=True,
|
|
434
555
|
)
|
|
435
556
|
|
|
436
|
-
# assume first column is start, second is key, and
|
|
557
|
+
# assume first column is start, second is stop, third is key, and fourth is filesize
|
|
437
558
|
# only assuming if not found in column names
|
|
438
559
|
# no error will be thrown if one of these missing, but per spec they are required
|
|
439
560
|
if "start" not in fr.columns.values:
|
|
440
561
|
fr.columns.values[0] = "start"
|
|
562
|
+
if "stop" not in fr.columns.values:
|
|
563
|
+
fr.columns.values[1] = "stop"
|
|
441
564
|
if "datakey" not in fr.columns.values:
|
|
442
|
-
fr.columns.values[
|
|
565
|
+
fr.columns.values[2] = "datakey"
|
|
443
566
|
if "filesize" not in fr.columns.values:
|
|
444
|
-
fr.columns.values[
|
|
567
|
+
fr.columns.values[3] = "filesize"
|
|
445
568
|
|
|
446
569
|
frs.append(fr)
|
|
447
570
|
|
|
@@ -449,14 +572,18 @@ class CloudCatalog:
|
|
|
449
572
|
|
|
450
573
|
# Filter catalog dataframe to exact requested dates
|
|
451
574
|
frs["start"] = pd.to_datetime(frs["start"], format="%Y-%m-%dT%H:%M:%SZ")
|
|
452
|
-
frs
|
|
575
|
+
frs["stop"] = pd.to_datetime(frs["stop"], format="%Y-%m-%dT%H:%M:%SZ")
|
|
576
|
+
# mod to add files that span a time interval longer than the requested interval
|
|
577
|
+
# was 'start date <= file_start & file_start < stop date'
|
|
578
|
+
# now 'start_date <= file_start & either file_start < stop date | file_end > start_date'
|
|
579
|
+
frs = frs[(start_date <= frs["start"]) & (frs["start"] < stop_date) | (frs["stop"] > start_date)]
|
|
453
580
|
|
|
454
581
|
return frs
|
|
455
582
|
|
|
456
583
|
@staticmethod
|
|
457
584
|
def stream(
|
|
458
585
|
cloud_catalog: pd.DataFrame,
|
|
459
|
-
process_func: Callable[[BytesIO, str, int], None],
|
|
586
|
+
process_func: Callable[[BytesIO, str, str, int], None],
|
|
460
587
|
ignore_faileds3get: bool = False,
|
|
461
588
|
) -> None:
|
|
462
589
|
"""
|
|
@@ -465,17 +592,21 @@ class CloudCatalog:
|
|
|
465
592
|
Parameters:
|
|
466
593
|
cloud_catalog (pd.DataFrame): A pandas DataFrame containing the dataset catalog information.
|
|
467
594
|
process_func (Callable): A function that takes a BytesIO object, a string representing the
|
|
468
|
-
start date of the file,
|
|
469
|
-
as arguments.
|
|
595
|
+
start date of the file, a string representing the stop date of the file,
|
|
596
|
+
and an integer representing the file size as arguments.
|
|
470
597
|
ignore_faileds3get (bool): A boolean that determines if the FailedS3Get is not thrown.
|
|
471
598
|
"""
|
|
472
|
-
s3_client = boto3.client("s3")
|
|
473
599
|
|
|
600
|
+
# original version, added https mod
|
|
601
|
+
#s3_client = boto3.client("s3")
|
|
602
|
+
|
|
603
|
+
|
|
474
604
|
fr_bytes_file = None
|
|
475
605
|
for _, row in cloud_catalog.iterrows():
|
|
476
606
|
# Get the S3 URL from the key in the dataframe
|
|
477
607
|
s3_url = row["datakey"]
|
|
478
608
|
|
|
609
|
+
""" # original version, added https mod
|
|
479
610
|
# Download the S3 file and read it into a BytesIO object
|
|
480
611
|
response = s3_client.get_object(
|
|
481
612
|
Bucket=s3_url.split("/")[2], Key="/".join(s3_url.split("/")[3:])
|
|
@@ -489,14 +620,15 @@ class CloudCatalog:
|
|
|
489
620
|
raise FailedS3Get(
|
|
490
621
|
f"Failed to get a cloud catalog object. Status: {stats}. Response: {response}"
|
|
491
622
|
)
|
|
492
|
-
|
|
623
|
+
"""
|
|
624
|
+
fr_bytes_file = fetch_S3orURL(s3_url,rawbytes=True)
|
|
493
625
|
# Pass the BytesIO object, start date, and file size to the processing function
|
|
494
626
|
# start may be a date object so making a string just in case for consistency
|
|
495
|
-
process_func(fr_bytes_file, str(row["start"]), row["filesize"])
|
|
627
|
+
process_func(fr_bytes_file, str(row["start"]), str(row["stop"]), row["filesize"])
|
|
496
628
|
|
|
497
629
|
@staticmethod
|
|
498
630
|
def stream_uri(
|
|
499
|
-
cloud_catalog: pd.DataFrame, process_func: Callable[[str, str, int], None]
|
|
631
|
+
cloud_catalog: pd.DataFrame, process_func: Callable[[str, str, str, int], None]
|
|
500
632
|
) -> None:
|
|
501
633
|
"""
|
|
502
634
|
Sends S3 URLs to a processing function.
|
|
@@ -504,7 +636,8 @@ class CloudCatalog:
|
|
|
504
636
|
Parameters:
|
|
505
637
|
cloud_catalog (pd.DataFrame): A pandas DataFrame containing the dataset catalog information.
|
|
506
638
|
process_func (Callable): A function that takes a string representing the S3 URL, a string
|
|
507
|
-
representing the start date of the file,
|
|
639
|
+
representing the start date of the file, a string representing the
|
|
640
|
+
stop date of the file, and an integer representing
|
|
508
641
|
the file size as arguments.
|
|
509
642
|
"""
|
|
510
643
|
for _, row in cloud_catalog.iterrows():
|
|
@@ -513,7 +646,7 @@ class CloudCatalog:
|
|
|
513
646
|
|
|
514
647
|
# Pass the S3 URL, start date, and file size to the processing function
|
|
515
648
|
# start may be a date object so making a string just in case for consistency
|
|
516
|
-
process_func(s3_url, str(row["start"]), row["filesize"])
|
|
649
|
+
process_func(s3_url, str(row["start"]), str(row["stop"]), row["filesize"])
|
|
517
650
|
|
|
518
651
|
|
|
519
652
|
class EntireCatalogSearch:
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import cloudcatalog
|
|
2
|
+
|
|
3
|
+
cr = cloudcatalog.CatalogRegistry()
|
|
4
|
+
endpoint = cr.get_endpoint('GSFC HelioCloud Public Temp')
|
|
5
|
+
fr = cloudcatalog.CloudCatalog(endpoint, cache=False)
|
|
6
|
+
bad="mms1_fpi_fast_des-dist"
|
|
7
|
+
s='2020-01-01T00:00'
|
|
8
|
+
e='2020-01-02T00:00'
|
|
9
|
+
keyset = fr.request_cloud_catalog(bad,start_date=s,stop_date=e)
|
cloudcatalog-0.4/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2023 heliocloud-data
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
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
|