Skip to content

Commit 89f4947

Browse files
committed
Merge branch 'release-2.9.0'
* release-2.9.0: (158 commits) Bump version to 2.9.0 Added underlying DynamoDB v2 support. Add redshift to setup.py/docs index Updated requests to something more modern. Only use 2 metadata service calls to get credentials Fix boto#1146: return response from custom url opener Fixed missing import. Add metadata_service_num_attempts config option Added cleanup for the snapshots created. Added support for redshift. Let total attempts by 1 + num_retries Add more diagnostics to debug logs Change GS calls to make_request to always convert to utf-8 bytes. Allow kwargs to be passed through to uplaoder Remove whitespace, fix long line lengths Improve VPC and VPN support Added sleeps to allow amazon time to propogate Added error handling for out of space during downloads Initial integration tests for idempotent subscribe Removed dead code from resumable upload handler ...
2 parents b5852b0 + 699d861 commit 89f4947

File tree

137 files changed

+13362
-1798
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

137 files changed

+13362
-1798
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ MANIFEST
1010
.idea
1111
.tox
1212
.coverage
13-
*flymake.py
13+
*flymake.py
14+
venv

README.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ Getting Started with Boto
137137
*************************
138138

139139
Your credentials can be passed into the methods that create
140-
connections. Alternatively, boto will check for the existance of the
140+
connections. Alternatively, boto will check for the existence of the
141141
following environment variables to ascertain your credentials:
142142

143143
**AWS_ACCESS_KEY_ID** - Your AWS Access Key ID

bin/dynamodb_dump

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
import errno
5+
import os
6+
7+
import boto
8+
from boto.compat import json
9+
10+
11+
DESCRIPTION = """Dump the contents of one or more DynamoDB tables to the local filesystem.
12+
13+
Each table is dumped into two files:
14+
- {table_name}.metadata stores the table's name, schema and provisioned
15+
throughput.
16+
- {table_name}.data stores the table's actual contents.
17+
18+
Both files are created in the current directory. To write them somewhere else,
19+
use the --out-dir parameter (the target directory will be created if needed).
20+
"""
21+
22+
23+
def dump_table(table, out_dir):
24+
metadata_file = os.path.join(out_dir, "%s.metadata" % table.name)
25+
data_file = os.path.join(out_dir, "%s.data" % table.name)
26+
27+
with open(metadata_file, "w") as metadata_fd:
28+
json.dump(
29+
{
30+
"name": table.name,
31+
"schema": table.schema.dict,
32+
"read_units": table.read_units,
33+
"write_units": table.write_units,
34+
},
35+
metadata_fd
36+
)
37+
38+
with open(data_file, "w") as data_fd:
39+
for item in table.scan():
40+
# JSON can't serialize sets -- convert those to lists.
41+
data = {}
42+
for k, v in item.iteritems():
43+
if isinstance(v, (set, frozenset)):
44+
data[k] = list(v)
45+
else:
46+
data[k] = v
47+
48+
data_fd.write(json.dumps(data))
49+
data_fd.write("\n")
50+
51+
52+
def dynamodb_dump(tables, out_dir):
53+
try:
54+
os.makedirs(out_dir)
55+
except OSError as e:
56+
# We don't care if the dir already exists.
57+
if e.errno != errno.EEXIST:
58+
raise
59+
60+
conn = boto.connect_dynamodb()
61+
for t in tables:
62+
dump_table(conn.get_table(t), out_dir)
63+
64+
65+
if __name__ == "__main__":
66+
parser = argparse.ArgumentParser(
67+
prog="dynamodb_dump",
68+
description=DESCRIPTION
69+
)
70+
parser.add_argument("--out-dir", default=".")
71+
parser.add_argument("tables", metavar="TABLES", nargs="+")
72+
73+
namespace = parser.parse_args()
74+
75+
dynamodb_dump(namespace.tables, namespace.out_dir)

bin/dynamodb_load

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
#!/usr/bin/env python
2+
3+
import argparse
4+
import os
5+
6+
import boto
7+
from boto.compat import json
8+
from boto.dynamodb.schema import Schema
9+
10+
11+
DESCRIPTION = """Load data into one or more DynamoDB tables.
12+
13+
For each table, data is read from two files:
14+
- {table_name}.metadata for the table's name, schema and provisioned
15+
throughput (only required if creating the table).
16+
- {table_name}.data for the table's actual contents.
17+
18+
Both files are searched for in the current directory. To read them from
19+
somewhere else, use the --in-dir parameter.
20+
21+
This program does not wipe the tables prior to loading data. However, any
22+
items present in the data files will overwrite the table's contents.
23+
"""
24+
25+
26+
def _json_iterload(fd):
27+
"""Lazily load newline-separated JSON objects from a file-like object."""
28+
buffer = ""
29+
eof = False
30+
while not eof:
31+
try:
32+
# Add a line to the buffer
33+
buffer += fd.next()
34+
except StopIteration:
35+
# We can't let that exception bubble up, otherwise the last
36+
# object in the file will never be decoded.
37+
eof = True
38+
try:
39+
# Try to decode a JSON object.
40+
json_object = json.loads(buffer.strip())
41+
42+
# Success: clear the buffer (everything was decoded).
43+
buffer = ""
44+
except ValueError:
45+
if eof and buffer.strip():
46+
# No more lines to load and the buffer contains something other
47+
# than whitespace: the file is, in fact, malformed.
48+
raise
49+
# We couldn't decode a complete JSON object: load more lines.
50+
continue
51+
52+
yield json_object
53+
54+
55+
def create_table(metadata_fd):
56+
"""Create a table from a metadata file-like object."""
57+
58+
59+
def load_table(table, in_fd):
60+
"""Load items into a table from a file-like object."""
61+
for i in _json_iterload(in_fd):
62+
# Convert lists back to sets.
63+
data = {}
64+
for k, v in i.iteritems():
65+
if isinstance(v, list):
66+
data[k] = set(v)
67+
else:
68+
data[k] = v
69+
table.new_item(attrs=i).put()
70+
71+
72+
def dynamodb_load(tables, in_dir, create_tables):
73+
conn = boto.connect_dynamodb()
74+
for t in tables:
75+
metadata_file = os.path.join(in_dir, "%s.metadata" % t)
76+
data_file = os.path.join(in_dir, "%s.data" % t)
77+
if create_tables:
78+
with open(metadata_file) as meta_fd:
79+
metadata = json.load(meta_fd)
80+
table = conn.create_table(
81+
name=t,
82+
schema=Schema(metadata["schema"]),
83+
read_units=metadata["read_units"],
84+
write_units=metadata["write_units"],
85+
)
86+
table.refresh(wait_for_active=True)
87+
else:
88+
table = conn.get_table(t)
89+
90+
with open(data_file) as in_fd:
91+
load_table(table, in_fd)
92+
93+
94+
if __name__ == "__main__":
95+
parser = argparse.ArgumentParser(
96+
prog="dynamodb_load",
97+
description=DESCRIPTION
98+
)
99+
parser.add_argument(
100+
"--create-tables",
101+
action="store_true",
102+
help="Create the tables if they don't exist already (without this flag, attempts to load data into non-existing tables fail)."
103+
)
104+
parser.add_argument("--in-dir", default=".")
105+
parser.add_argument("tables", metavar="TABLES", nargs="+")
106+
107+
namespace = parser.parse_args()
108+
109+
dynamodb_load(namespace.tables, namespace.in_dir, namespace.create_tables)

bin/mturk

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import os.path
2525
import string
2626
import inspect
2727
import datetime, calendar
28-
import json
2928
import boto.mturk.connection, boto.mturk.price, boto.mturk.question, boto.mturk.qualification
29+
from boto.compat import json
3030

3131
# --------------------------------------------------
3232
# Globals

bin/sdbadmin

+1-9
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,7 @@ VERSION = "%prog version 1.0"
2626
import boto
2727
import time
2828
from boto import sdb
29-
30-
# Allow support for JSON
31-
try:
32-
import simplejson as json
33-
except:
34-
try:
35-
import json
36-
except:
37-
json = False
29+
from boto.compat import json
3830

3931
def choice_input(options, default=None, title=None):
4032
"""

boto/__init__.py

+55-14
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Copyright (c) 2010-2011, Eucalyptus Systems, Inc.
33
# Copyright (c) 2011, Nexenta Systems Inc.
44
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates.
5+
# Copyright (c) 2010, Google, Inc.
56
# All rights reserved.
67
#
78
# Permission is hereby granted, free of charge, to any person obtaining a
@@ -35,12 +36,20 @@
3536
import urlparse
3637
from boto.exception import InvalidUriError
3738

38-
__version__ = '2.8.0'
39+
__version__ = '2.9.0'
3940
Version = __version__ # for backware compatibility
4041

4142
UserAgent = 'Boto/%s (%s)' % (__version__, sys.platform)
4243
config = Config()
4344

45+
# Regex to disallow buckets violating charset or not [3..255] chars total.
46+
BUCKET_NAME_RE = re.compile(r'^[a-z0-9][a-z0-9\._-]{1,253}[a-z0-9]$')
47+
# Regex to disallow buckets with individual DNS labels longer than 63.
48+
TOO_LONG_DNS_NAME_COMP = re.compile(r'[-_a-z0-9]{64}')
49+
GENERATION_RE = re.compile(r'(?P<versionless_uri_str>.+)'
50+
r'#(?P<generation>[0-9]+)$')
51+
VERSION_RE = re.compile('(?P<versionless_uri_str>.+)#(?P<version_id>.+)$')
52+
4453

4554
def init_logging():
4655
for file in BotoConfigLocations:
@@ -655,9 +664,19 @@ def connect_elastictranscoder(aws_access_key_id=None,
655664
**kwargs)
656665

657666

667+
def connect_opsworks(aws_access_key_id=None,
668+
aws_secret_access_key=None,
669+
**kwargs):
670+
from boto.opsworks.layer1 import OpsWorksConnection
671+
return OpsWorksConnection(
672+
aws_access_key_id=aws_access_key_id,
673+
aws_secret_access_key=aws_secret_access_key,
674+
**kwargs)
675+
676+
658677
def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
659678
bucket_storage_uri_class=BucketStorageUri,
660-
suppress_consec_slashes=True):
679+
suppress_consec_slashes=True, is_latest=False):
661680
"""
662681
Instantiate a StorageUri from a URI string.
663682
@@ -673,6 +692,9 @@ def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
673692
:param bucket_storage_uri_class: Allows mocking for unit tests.
674693
:param suppress_consec_slashes: If provided, controls whether
675694
consecutive slashes will be suppressed in key paths.
695+
:type is_latest: bool
696+
:param is_latest: whether this versioned object represents the
697+
current version.
676698
677699
We allow validate to be disabled to allow caller
678700
to implement bucket-level wildcarding (outside the boto library;
@@ -684,14 +706,17 @@ def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
684706
``uri_str`` must be one of the following formats:
685707
686708
* gs://bucket/name
709+
* gs://bucket/name#ver
687710
* s3://bucket/name
688711
* gs://bucket
689712
* s3://bucket
690713
* filename (which could be a Unix path like /a/b/c or a Windows path like
691714
C:\a\b\c)
692715
693-
The last example uses the default scheme ('file', unless overridden)
716+
The last example uses the default scheme ('file', unless overridden).
694717
"""
718+
version_id = None
719+
generation = None
695720

696721
# Manually parse URI components instead of using urlparse.urlparse because
697722
# what we're calling URIs don't really fit the standard syntax for URIs
@@ -708,7 +733,8 @@ def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
708733
if not (platform.system().lower().startswith('windows')
709734
and colon_pos == 1
710735
and drive_char >= 'a' and drive_char <= 'z'):
711-
raise InvalidUriError('"%s" contains ":" instead of "://"' % uri_str)
736+
raise InvalidUriError('"%s" contains ":" instead of "://"' %
737+
uri_str)
712738
scheme = default_scheme.lower()
713739
path = uri_str
714740
else:
@@ -727,23 +753,38 @@ def storage_uri(uri_str, default_scheme='file', debug=0, validate=True,
727753
else:
728754
path_parts = path.split('/', 1)
729755
bucket_name = path_parts[0]
730-
if (validate and bucket_name and
731-
# Disallow buckets violating charset or not [3..255] chars total.
732-
(not re.match('^[a-z0-9][a-z0-9\._-]{1,253}[a-z0-9]$', bucket_name)
733-
# Disallow buckets with individual DNS labels longer than 63.
734-
or re.search('[-_a-z0-9]{64}', bucket_name))):
735-
raise InvalidUriError('Invalid bucket name in URI "%s"' % uri_str)
736-
# If enabled, ensure the bucket name is valid, to avoid possibly
737-
# confusing other parts of the code. (For example if we didn't
756+
object_name = ''
757+
# If validate enabled, ensure the bucket name is valid, to avoid
758+
# possibly confusing other parts of the code. (For example if we didn't
738759
# catch bucket names containing ':', when a user tried to connect to
739760
# the server with that name they might get a confusing error about
740761
# non-integer port numbers.)
741-
object_name = ''
762+
if (validate and bucket_name and
763+
(not BUCKET_NAME_RE.match(bucket_name)
764+
or TOO_LONG_DNS_NAME_COMP.search(bucket_name))):
765+
raise InvalidUriError('Invalid bucket name in URI "%s"' % uri_str)
766+
if scheme == 'gs':
767+
match = GENERATION_RE.search(path)
768+
if match:
769+
md = match.groupdict()
770+
versionless_uri_str = md['versionless_uri_str']
771+
path_parts = versionless_uri_str.split('/', 1)
772+
generation = int(md['generation'])
773+
elif scheme == 's3':
774+
match = VERSION_RE.search(path)
775+
if match:
776+
md = match.groupdict()
777+
versionless_uri_str = md['versionless_uri_str']
778+
path_parts = versionless_uri_str.split('/', 1)
779+
version_id = md['version_id']
780+
else:
781+
raise InvalidUriError('Unrecognized scheme "%s"' % scheme)
742782
if len(path_parts) > 1:
743783
object_name = path_parts[1]
744784
return bucket_storage_uri_class(
745785
scheme, bucket_name, object_name, debug,
746-
suppress_consec_slashes=suppress_consec_slashes)
786+
suppress_consec_slashes=suppress_consec_slashes,
787+
version_id=version_id, generation=generation, is_latest=is_latest)
747788

748789

749790
def storage_uri_for_key(key):

boto/auth.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ def add_auth(self, http_request, **kwargs):
164164
boto.log.debug('StringToSign:\n%s' % string_to_sign)
165165
b64_hmac = self.sign_string(string_to_sign)
166166
auth_hdr = self._provider.auth_header
167-
headers['Authorization'] = ("%s %s:%s" %
168-
(auth_hdr,
169-
self._provider.access_key, b64_hmac))
167+
auth = ("%s %s:%s" % (auth_hdr, self._provider.access_key, b64_hmac))
168+
boto.log.debug('Signature:\n%s' % auth)
169+
headers['Authorization'] = auth
170170

171171

172172
class HmacAuthV2Handler(AuthHandler, HmacKeys):

boto/beanstalk/exception.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import sys
2-
import json
2+
from boto.compat import json
33
from boto.exception import BotoServerError
44

55

0 commit comments

Comments
 (0)