Skip to content
This repository was archived by the owner on Dec 10, 2019. It is now read-only.

Commit 08ed514

Browse files
authored
Support local file uploads from client (#15)
* Support local file uploads from client
1 parent 374c792 commit 08ed514

File tree

4 files changed

+195
-1
lines changed

4 files changed

+195
-1
lines changed

rasterfoundry/models/tests/__init__.py

Whitespace-only changes.
+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import os
2+
import shutil
3+
import random
4+
from string import ascii_letters
5+
6+
7+
import pytest
8+
9+
10+
from ..upload import Upload
11+
12+
13+
@pytest.fixture
14+
def datasource():
15+
return 'fooDat'
16+
17+
18+
@pytest.fixture
19+
def organization():
20+
return 'fooOrg'
21+
22+
23+
@pytest.fixture
24+
def random_tifs():
25+
return [
26+
''.join([random.choice(ascii_letters) for _ in range(10)]) + '.tif'
27+
for _ in range(10)
28+
]
29+
30+
31+
@pytest.fixture
32+
def bogus_bucket():
33+
return 'fooBucket'
34+
35+
36+
@pytest.fixture
37+
def bogus_prefix():
38+
return 'fooPrefix'
39+
40+
41+
def test_file_globbing(datasource, organization, random_tifs, bogus_bucket,
42+
bogus_prefix):
43+
tifs = os.path.join('/tmp', 'tifs')
44+
other_tifs = os.path.join('/tmp', 'other_tifs')
45+
os.mkdir(tifs)
46+
os.mkdir(other_tifs)
47+
paths = []
48+
for i, t in enumerate(random_tifs):
49+
if i % 2 == 0:
50+
out_path = os.path.join(tifs, t)
51+
else:
52+
out_path = os.path.join(other_tifs, t)
53+
with open(out_path, 'w') as outf:
54+
outf.write('a tif')
55+
paths.append(out_path)
56+
57+
upload_create = Upload.upload_create_from_files(
58+
datasource, organization, '/tmp/**/*.tif', bogus_bucket, bogus_prefix,
59+
dry_run=True
60+
)
61+
upload_fnames = [os.path.split(f)[-1] for f in upload_create['files']]
62+
src_fnames = [os.path.split(f)[-1] for f in paths]
63+
assert set(upload_fnames) == set(src_fnames)
64+
65+
shutil.rmtree(tifs)
66+
shutil.rmtree(other_tifs)
67+
68+
69+
def test_no_file_globbing(datasource, organization, bogus_bucket,
70+
bogus_prefix):
71+
files = ['bar.tif', 'foo.tif']
72+
upload_create = Upload.upload_create_from_files(
73+
datasource, organization, files, bogus_bucket, bogus_prefix,
74+
dry_run=True
75+
)
76+
77+
upload_fnames = [os.path.split(f)[-1] for f in upload_create['files']]
78+
assert upload_fnames == files

rasterfoundry/models/upload.py

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""An Upload is raw data to be transformed into a Scene"""
2+
import glob
3+
import os
4+
5+
import boto3
6+
7+
8+
class Upload(object):
9+
"""A Raster Foundry upload"""
10+
11+
s3_client = boto3.client('s3')
12+
13+
def __repr__(self):
14+
return '<Upload - {}>'.format(self.name)
15+
16+
def __init__(self, upload, api):
17+
"""Instantiate a new Upload
18+
19+
Args:
20+
upload (Upload): generated Upload object from specification
21+
api (API): api used to make requests on behalf of an upload
22+
"""
23+
24+
self._upload = upload
25+
self.api = api
26+
27+
self.id = upload.id
28+
self.upload_type = upload.uploadType
29+
self.metadata = upload.metadata
30+
self.files = upload.files
31+
32+
@classmethod
33+
def upload_create_from_files(
34+
cls, datasource, organization, paths_to_tifs,
35+
dest_bucket, dest_prefix, metadata={}, visibility='PRIVATE',
36+
project_id=None, dry_run=False
37+
):
38+
"""Create an Upload from a set of tifs
39+
40+
Args:
41+
datasource (str): UUID of the datasource this upload belongs to
42+
organization (str): UUID of the organization this upload belongs to
43+
paths_to_tifs (str | str[]): which tifs to upload. If passed a
44+
string, files will be the unix path expansion of the passed
45+
string, e.g., '*.tif' will become an array of all of the tifs
46+
in the current working directory. If passed a list, files will
47+
be exactly those files in the list.
48+
dest_bucket (str): s3 bucket to upload local files to. If the
49+
Raster Foundry application does not have permission to read
50+
from this location, the upload will fail to process.
51+
dest_prefix (str): s3 prefix to upload local files to. If the
52+
Raster Foundry application does not have permission to read
53+
from this location, the upload will fail to process.
54+
metadata (dict): Additional information to store with this upload.
55+
acquisitionDate and cloudCover will be parsed into any created
56+
scenes.
57+
visibility (str): PUBLIC, PRIVATE, or ORGANIZATION visibility level
58+
for the created scenes
59+
project_id (str): UUID of the project scenes from this upload
60+
should be added to
61+
dry_run (bool): whether to perform side-effecting actions like
62+
uploads to s3
63+
64+
Returns:
65+
dict: splattable object to post to /uploads/
66+
"""
67+
if isinstance(paths_to_tifs, str):
68+
paths = glob.glob(paths_to_tifs)
69+
else:
70+
paths = paths_to_tifs
71+
upload_status = 'UPLOADED'
72+
file_type = 'GEOTIFF'
73+
74+
files = []
75+
for f in paths:
76+
fname = os.path.split(f)[-1]
77+
key = '/'.join([x for x in [dest_prefix, fname] if x])
78+
dest_path = 's3://' + '/'.join(
79+
[x for x in [dest_bucket, dest_prefix, fname] if x]
80+
)
81+
if not dry_run:
82+
with open(f, 'r') as inf:
83+
cls.s3_client.put_object(
84+
Body=inf.read(),
85+
Bucket=dest_bucket,
86+
Key=key
87+
)
88+
files.append(dest_path)
89+
90+
return dict(
91+
uploadStatus=upload_status,
92+
files=files,
93+
uploadType='S3',
94+
fileType=file_type,
95+
datasource=datasource,
96+
organizationId=organization,
97+
metadata=metadata,
98+
visibility=visibility,
99+
projectId=project_id
100+
)
101+
102+
@classmethod
103+
def create(cls, api, upload_create):
104+
"""Post an upload to Raster Foundry for processing
105+
106+
Args:
107+
api (API): API to use for requests
108+
upload_create (dict): post parameters for /uploads. See
109+
upload_create_from_files
110+
111+
Returns:
112+
Upload: created object in Raster Foundry
113+
"""
114+
115+
return api.client.Imagery.post_uploads(Upload=upload_create).result()

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
'cryptography == 1.8.1',
2525
'pyasn1 >= 0.2.3',
2626
'requests >= 2.9.1',
27-
'bravado >= 8.4.0'
27+
'bravado >= 8.4.0',
28+
'boto3 >= 1.4.4'
2829
],
2930
extras_require={
3031
'notebook': [

0 commit comments

Comments
 (0)