Mock more of the Glue Data Catalog APIs
This adds some of the missing Get/Update/Create APIs relating to the Glue data catalog -- but not yet all of them, and none of the Batch* API calls.
This commit is contained in:
parent
dfa7935e13
commit
5783d66206
@ -6,19 +6,56 @@ class GlueClientError(JsonRESTError):
|
|||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
|
|
||||||
class DatabaseAlreadyExistsException(GlueClientError):
|
class AlreadyExistsException(GlueClientError):
|
||||||
def __init__(self):
|
def __init__(self, typ):
|
||||||
self.code = 400
|
super(GlueClientError, self).__init__(
|
||||||
super(DatabaseAlreadyExistsException, self).__init__(
|
'AlreadyExistsException',
|
||||||
'DatabaseAlreadyExistsException',
|
'%s already exists.' % (typ),
|
||||||
'Database already exists.'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TableAlreadyExistsException(GlueClientError):
|
class DatabaseAlreadyExistsException(AlreadyExistsException):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.code = 400
|
super(DatabaseAlreadyExistsException, self).__init__('Database')
|
||||||
super(TableAlreadyExistsException, self).__init__(
|
|
||||||
'TableAlreadyExistsException',
|
|
||||||
'Table already exists.'
|
class TableAlreadyExistsException(AlreadyExistsException):
|
||||||
|
def __init__(self):
|
||||||
|
super(TableAlreadyExistsException, self).__init__('Table')
|
||||||
|
|
||||||
|
|
||||||
|
class PartitionAlreadyExistsException(AlreadyExistsException):
|
||||||
|
def __init__(self):
|
||||||
|
super(PartitionAlreadyExistsException, self).__init__('Partition')
|
||||||
|
|
||||||
|
|
||||||
|
class EntityNotFoundException(GlueClientError):
|
||||||
|
def __init__(self, msg):
|
||||||
|
super(GlueClientError, self).__init__(
|
||||||
|
'EntityNotFoundException',
|
||||||
|
msg,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseNotFoundException(EntityNotFoundException):
|
||||||
|
def __init__(self, db):
|
||||||
|
super(DatabaseNotFoundException, self).__init__(
|
||||||
|
'Database %s not found.' % db,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TableNotFoundException(EntityNotFoundException):
|
||||||
|
def __init__(self, tbl):
|
||||||
|
super(TableNotFoundException, self).__init__(
|
||||||
|
'Table %s not found.' % tbl,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PartitionNotFoundException(EntityNotFoundException):
|
||||||
|
def __init__(self):
|
||||||
|
super(PartitionNotFoundException, self).__init__("Cannot find partition.")
|
||||||
|
|
||||||
|
|
||||||
|
class VersionNotFoundException(EntityNotFoundException):
|
||||||
|
def __init__(self):
|
||||||
|
super(VersionNotFoundException, self).__init__("Version not found.")
|
||||||
|
@ -1,8 +1,19 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from.exceptions import DatabaseAlreadyExistsException, TableAlreadyExistsException
|
from.exceptions import (
|
||||||
|
JsonRESTError,
|
||||||
|
DatabaseAlreadyExistsException,
|
||||||
|
DatabaseNotFoundException,
|
||||||
|
TableAlreadyExistsException,
|
||||||
|
TableNotFoundException,
|
||||||
|
PartitionAlreadyExistsException,
|
||||||
|
PartitionNotFoundException,
|
||||||
|
VersionNotFoundException,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class GlueBackend(BaseBackend):
|
class GlueBackend(BaseBackend):
|
||||||
@ -19,7 +30,10 @@ class GlueBackend(BaseBackend):
|
|||||||
return database
|
return database
|
||||||
|
|
||||||
def get_database(self, database_name):
|
def get_database(self, database_name):
|
||||||
|
try:
|
||||||
return self.databases[database_name]
|
return self.databases[database_name]
|
||||||
|
except KeyError:
|
||||||
|
raise DatabaseNotFoundException(database_name)
|
||||||
|
|
||||||
def create_table(self, database_name, table_name, table_input):
|
def create_table(self, database_name, table_name, table_input):
|
||||||
database = self.get_database(database_name)
|
database = self.get_database(database_name)
|
||||||
@ -33,7 +47,10 @@ class GlueBackend(BaseBackend):
|
|||||||
|
|
||||||
def get_table(self, database_name, table_name):
|
def get_table(self, database_name, table_name):
|
||||||
database = self.get_database(database_name)
|
database = self.get_database(database_name)
|
||||||
|
try:
|
||||||
return database.tables[table_name]
|
return database.tables[table_name]
|
||||||
|
except KeyError:
|
||||||
|
raise TableNotFoundException(table_name)
|
||||||
|
|
||||||
def get_tables(self, database_name):
|
def get_tables(self, database_name):
|
||||||
database = self.get_database(database_name)
|
database = self.get_database(database_name)
|
||||||
@ -52,9 +69,84 @@ class FakeTable(BaseModel):
|
|||||||
def __init__(self, database_name, table_name, table_input):
|
def __init__(self, database_name, table_name, table_input):
|
||||||
self.database_name = database_name
|
self.database_name = database_name
|
||||||
self.name = table_name
|
self.name = table_name
|
||||||
self.table_input = table_input
|
self.partitions = OrderedDict()
|
||||||
self.storage_descriptor = self.table_input.get('StorageDescriptor', {})
|
self.versions = []
|
||||||
self.partition_keys = self.table_input.get('PartitionKeys', [])
|
self.update(table_input)
|
||||||
|
|
||||||
|
def update(self, table_input):
|
||||||
|
self.versions.append(table_input)
|
||||||
|
|
||||||
|
def get_version(self, ver):
|
||||||
|
try:
|
||||||
|
if not isinstance(ver, int):
|
||||||
|
# "1" goes to [0]
|
||||||
|
ver = int(ver) - 1
|
||||||
|
except ValueError as e:
|
||||||
|
raise JsonRESTError("InvalidInputException", str(e))
|
||||||
|
|
||||||
|
try:
|
||||||
|
return self.versions[ver]
|
||||||
|
except IndexError:
|
||||||
|
raise VersionNotFoundException()
|
||||||
|
|
||||||
|
def as_dict(self, version=-1):
|
||||||
|
obj = {
|
||||||
|
'DatabaseName': self.database_name,
|
||||||
|
'Name': self.name,
|
||||||
|
}
|
||||||
|
obj.update(self.get_version(version))
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def create_partition(self, partiton_input):
|
||||||
|
partition = FakePartition(self.database_name, self.name, partiton_input)
|
||||||
|
key = str(partition.values)
|
||||||
|
if key in self.partitions:
|
||||||
|
raise PartitionAlreadyExistsException()
|
||||||
|
self.partitions[str(partition.values)] = partition
|
||||||
|
|
||||||
|
def get_partitions(self):
|
||||||
|
return [p for str_part_values, p in self.partitions.items()]
|
||||||
|
|
||||||
|
def get_partition(self, values):
|
||||||
|
try:
|
||||||
|
return self.partitions[str(values)]
|
||||||
|
except KeyError:
|
||||||
|
raise PartitionNotFoundException()
|
||||||
|
|
||||||
|
def update_partition(self, old_values, partiton_input):
|
||||||
|
partition = FakePartition(self.database_name, self.name, partiton_input)
|
||||||
|
key = str(partition.values)
|
||||||
|
if old_values == partiton_input['Values']:
|
||||||
|
# Altering a partition in place. Don't remove it so the order of
|
||||||
|
# returned partitions doesn't change
|
||||||
|
if key not in self.partitions:
|
||||||
|
raise PartitionNotFoundException()
|
||||||
|
else:
|
||||||
|
removed = self.partitions.pop(str(old_values), None)
|
||||||
|
if removed is None:
|
||||||
|
raise PartitionNotFoundException()
|
||||||
|
if key in self.partitions:
|
||||||
|
# Trying to update to overwrite a partition that exists
|
||||||
|
raise PartitionAlreadyExistsException()
|
||||||
|
self.partitions[key] = partition
|
||||||
|
|
||||||
|
|
||||||
|
class FakePartition(BaseModel):
|
||||||
|
def __init__(self, database_name, table_name, partiton_input):
|
||||||
|
self.creation_time = time.time()
|
||||||
|
self.database_name = database_name
|
||||||
|
self.table_name = table_name
|
||||||
|
self.partition_input = partiton_input
|
||||||
|
self.values = self.partition_input.get('Values', [])
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
obj = {
|
||||||
|
'DatabaseName': self.database_name,
|
||||||
|
'TableName': self.table_name,
|
||||||
|
'CreationTime': self.creation_time,
|
||||||
|
}
|
||||||
|
obj.update(self.partition_input)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
glue_backend = GlueBackend()
|
glue_backend = GlueBackend()
|
||||||
|
@ -37,27 +37,94 @@ class GlueResponse(BaseResponse):
|
|||||||
database_name = self.parameters.get('DatabaseName')
|
database_name = self.parameters.get('DatabaseName')
|
||||||
table_name = self.parameters.get('Name')
|
table_name = self.parameters.get('Name')
|
||||||
table = self.glue_backend.get_table(database_name, table_name)
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
|
||||||
|
return json.dumps({'Table': table.as_dict()})
|
||||||
|
|
||||||
|
def update_table(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_input = self.parameters.get('TableInput')
|
||||||
|
table_name = table_input.get('Name')
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
table.update(table_input)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def get_table_versions(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_name = self.parameters.get('TableName')
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
|
||||||
return json.dumps({
|
return json.dumps({
|
||||||
'Table': {
|
"TableVersions": [
|
||||||
'DatabaseName': table.database_name,
|
{
|
||||||
'Name': table.name,
|
"Table": table.as_dict(version=n),
|
||||||
'PartitionKeys': table.partition_keys,
|
"VersionId": str(n + 1),
|
||||||
'StorageDescriptor': table.storage_descriptor
|
} for n in range(len(table.versions))
|
||||||
}
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_table_version(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_name = self.parameters.get('TableName')
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
ver_id = self.parameters.get('VersionId')
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
"TableVersion": {
|
||||||
|
"Table": table.as_dict(version=ver_id),
|
||||||
|
"VersionId": ver_id,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
def get_tables(self):
|
def get_tables(self):
|
||||||
database_name = self.parameters.get('DatabaseName')
|
database_name = self.parameters.get('DatabaseName')
|
||||||
tables = self.glue_backend.get_tables(database_name)
|
tables = self.glue_backend.get_tables(database_name)
|
||||||
return json.dumps(
|
return json.dumps({
|
||||||
{
|
|
||||||
'TableList': [
|
'TableList': [
|
||||||
{
|
table.as_dict() for table in tables
|
||||||
'DatabaseName': table.database_name,
|
|
||||||
'Name': table.name,
|
|
||||||
'PartitionKeys': table.partition_keys,
|
|
||||||
'StorageDescriptor': table.storage_descriptor
|
|
||||||
} for table in tables
|
|
||||||
]
|
]
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
def get_partitions(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_name = self.parameters.get('TableName')
|
||||||
|
if 'Expression' in self.parameters:
|
||||||
|
raise NotImplementedError("Expression filtering in get_partitions is not implemented in moto")
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
|
||||||
|
return json.dumps({
|
||||||
|
'Partitions': [
|
||||||
|
p.as_dict() for p in table.get_partitions()
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_partition(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_name = self.parameters.get('TableName')
|
||||||
|
values = self.parameters.get('PartitionValues')
|
||||||
|
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
|
||||||
|
p = table.get_partition(values)
|
||||||
|
|
||||||
|
return json.dumps({'Partition': p.as_dict()})
|
||||||
|
|
||||||
|
def create_partition(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_name = self.parameters.get('TableName')
|
||||||
|
part_input = self.parameters.get('PartitionInput')
|
||||||
|
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
table.create_partition(part_input)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def update_partition(self):
|
||||||
|
database_name = self.parameters.get('DatabaseName')
|
||||||
|
table_name = self.parameters.get('TableName')
|
||||||
|
part_input = self.parameters.get('PartitionInput')
|
||||||
|
part_to_update = self.parameters.get('PartitionValueList')
|
||||||
|
|
||||||
|
table = self.glue_backend.get_table(database_name, table_name)
|
||||||
|
table.update_partition(part_to_update, part_input)
|
||||||
|
|
||||||
|
return ""
|
||||||
|
@ -29,3 +29,28 @@ TABLE_INPUT = {
|
|||||||
},
|
},
|
||||||
'TableType': 'EXTERNAL_TABLE',
|
'TableType': 'EXTERNAL_TABLE',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
PARTITION_INPUT = {
|
||||||
|
# 'DatabaseName': 'dbname',
|
||||||
|
'StorageDescriptor': {
|
||||||
|
'BucketColumns': [],
|
||||||
|
'Columns': [],
|
||||||
|
'Compressed': False,
|
||||||
|
'InputFormat': 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat',
|
||||||
|
'Location': 's3://.../partition=value',
|
||||||
|
'NumberOfBuckets': -1,
|
||||||
|
'OutputFormat': 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat',
|
||||||
|
'Parameters': {},
|
||||||
|
'SerdeInfo': {
|
||||||
|
'Parameters': {'path': 's3://...', 'serialization.format': '1'},
|
||||||
|
'SerializationLibrary': 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'},
|
||||||
|
'SkewedInfo': {'SkewedColumnNames': [],
|
||||||
|
'SkewedColumnValueLocationMaps': {},
|
||||||
|
'SkewedColumnValues': []},
|
||||||
|
'SortColumns': [],
|
||||||
|
'StoredAsSubDirectories': False,
|
||||||
|
},
|
||||||
|
# 'TableName': 'source_table',
|
||||||
|
# 'Values': ['2018-06-26'],
|
||||||
|
}
|
||||||
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from .fixtures.datacatalog import TABLE_INPUT
|
from .fixtures.datacatalog import TABLE_INPUT, PARTITION_INPUT
|
||||||
|
|
||||||
|
|
||||||
def create_database(client, database_name):
|
def create_database(client, database_name):
|
||||||
@ -17,22 +17,38 @@ def get_database(client, database_name):
|
|||||||
return client.get_database(Name=database_name)
|
return client.get_database(Name=database_name)
|
||||||
|
|
||||||
|
|
||||||
def create_table_input(table_name, s3_location, columns=[], partition_keys=[]):
|
def create_table_input(database_name, table_name, columns=[], partition_keys=[]):
|
||||||
table_input = copy.deepcopy(TABLE_INPUT)
|
table_input = copy.deepcopy(TABLE_INPUT)
|
||||||
table_input['Name'] = table_name
|
table_input['Name'] = table_name
|
||||||
table_input['PartitionKeys'] = partition_keys
|
table_input['PartitionKeys'] = partition_keys
|
||||||
table_input['StorageDescriptor']['Columns'] = columns
|
table_input['StorageDescriptor']['Columns'] = columns
|
||||||
table_input['StorageDescriptor']['Location'] = s3_location
|
table_input['StorageDescriptor']['Location'] = 's3://my-bucket/{database_name}/{table_name}'.format(
|
||||||
|
database_name=database_name,
|
||||||
|
table_name=table_name
|
||||||
|
)
|
||||||
return table_input
|
return table_input
|
||||||
|
|
||||||
|
|
||||||
def create_table(client, database_name, table_name, table_input):
|
def create_table(client, database_name, table_name, table_input=None, **kwargs):
|
||||||
|
if table_input is None:
|
||||||
|
table_input = create_table_input(database_name, table_name, **kwargs)
|
||||||
|
|
||||||
return client.create_table(
|
return client.create_table(
|
||||||
DatabaseName=database_name,
|
DatabaseName=database_name,
|
||||||
TableInput=table_input
|
TableInput=table_input
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_table(client, database_name, table_name, table_input=None, **kwargs):
|
||||||
|
if table_input is None:
|
||||||
|
table_input = create_table_input(database_name, table_name, **kwargs)
|
||||||
|
|
||||||
|
return client.update_table(
|
||||||
|
DatabaseName=database_name,
|
||||||
|
TableInput=table_input,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_table(client, database_name, table_name):
|
def get_table(client, database_name, table_name):
|
||||||
return client.get_table(
|
return client.get_table(
|
||||||
DatabaseName=database_name,
|
DatabaseName=database_name,
|
||||||
@ -44,3 +60,60 @@ def get_tables(client, database_name):
|
|||||||
return client.get_tables(
|
return client.get_tables(
|
||||||
DatabaseName=database_name
|
DatabaseName=database_name
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_versions(client, database_name, table_name):
|
||||||
|
return client.get_table_versions(
|
||||||
|
DatabaseName=database_name,
|
||||||
|
TableName=table_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_table_version(client, database_name, table_name, version_id):
|
||||||
|
return client.get_table_version(
|
||||||
|
DatabaseName=database_name,
|
||||||
|
TableName=table_name,
|
||||||
|
VersionId=version_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def create_partition_input(database_name, table_name, values=[], columns=[]):
|
||||||
|
root_path = 's3://my-bucket/{database_name}/{table_name}'.format(
|
||||||
|
database_name=database_name,
|
||||||
|
table_name=table_name
|
||||||
|
)
|
||||||
|
|
||||||
|
part_input = copy.deepcopy(PARTITION_INPUT)
|
||||||
|
part_input['Values'] = values
|
||||||
|
part_input['StorageDescriptor']['Columns'] = columns
|
||||||
|
part_input['StorageDescriptor']['SerdeInfo']['Parameters']['path'] = root_path
|
||||||
|
return part_input
|
||||||
|
|
||||||
|
|
||||||
|
def create_partition(client, database_name, table_name, partiton_input=None, **kwargs):
|
||||||
|
if partiton_input is None:
|
||||||
|
partiton_input = create_partition_input(database_name, table_name, **kwargs)
|
||||||
|
return client.create_partition(
|
||||||
|
DatabaseName=database_name,
|
||||||
|
TableName=table_name,
|
||||||
|
PartitionInput=partiton_input
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def update_partition(client, database_name, table_name, old_values=[], partiton_input=None, **kwargs):
|
||||||
|
if partiton_input is None:
|
||||||
|
partiton_input = create_partition_input(database_name, table_name, **kwargs)
|
||||||
|
return client.update_partition(
|
||||||
|
DatabaseName=database_name,
|
||||||
|
TableName=table_name,
|
||||||
|
PartitionInput=partiton_input,
|
||||||
|
PartitionValueList=old_values,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_partition(client, database_name, table_name, values):
|
||||||
|
return client.get_partition(
|
||||||
|
DatabaseName=database_name,
|
||||||
|
TableName=table_name,
|
||||||
|
PartitionValues=values,
|
||||||
|
)
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
import re
|
||||||
from nose.tools import assert_raises
|
from nose.tools import assert_raises
|
||||||
import boto3
|
import boto3
|
||||||
from botocore.client import ClientError
|
from botocore.client import ClientError
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
import pytz
|
||||||
|
|
||||||
from moto import mock_glue
|
from moto import mock_glue
|
||||||
from . import helpers
|
from . import helpers
|
||||||
|
|
||||||
@ -30,7 +35,19 @@ def test_create_database_already_exists():
|
|||||||
with assert_raises(ClientError) as exc:
|
with assert_raises(ClientError) as exc:
|
||||||
helpers.create_database(client, database_name)
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
exc.exception.response['Error']['Code'].should.equal('DatabaseAlreadyExistsException')
|
exc.exception.response['Error']['Code'].should.equal('AlreadyExistsException')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_database_not_exits():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'nosuchdatabase'
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_database(client, database_name)
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('Database nosuchdatabase not found')
|
||||||
|
|
||||||
|
|
||||||
@mock_glue
|
@mock_glue
|
||||||
@ -40,12 +57,7 @@ def test_create_table():
|
|||||||
helpers.create_database(client, database_name)
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
table_name = 'myspecialtable'
|
table_name = 'myspecialtable'
|
||||||
s3_location = 's3://my-bucket/{database_name}/{table_name}'.format(
|
table_input = helpers.create_table_input(database_name, table_name)
|
||||||
database_name=database_name,
|
|
||||||
table_name=table_name
|
|
||||||
)
|
|
||||||
|
|
||||||
table_input = helpers.create_table_input(table_name, s3_location)
|
|
||||||
helpers.create_table(client, database_name, table_name, table_input)
|
helpers.create_table(client, database_name, table_name, table_input)
|
||||||
|
|
||||||
response = helpers.get_table(client, database_name, table_name)
|
response = helpers.get_table(client, database_name, table_name)
|
||||||
@ -63,18 +75,12 @@ def test_create_table_already_exists():
|
|||||||
helpers.create_database(client, database_name)
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
table_name = 'cantcreatethistabletwice'
|
table_name = 'cantcreatethistabletwice'
|
||||||
s3_location = 's3://my-bucket/{database_name}/{table_name}'.format(
|
helpers.create_table(client, database_name, table_name)
|
||||||
database_name=database_name,
|
|
||||||
table_name=table_name
|
|
||||||
)
|
|
||||||
|
|
||||||
table_input = helpers.create_table_input(table_name, s3_location)
|
|
||||||
helpers.create_table(client, database_name, table_name, table_input)
|
|
||||||
|
|
||||||
with assert_raises(ClientError) as exc:
|
with assert_raises(ClientError) as exc:
|
||||||
helpers.create_table(client, database_name, table_name, table_input)
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
exc.exception.response['Error']['Code'].should.equal('TableAlreadyExistsException')
|
exc.exception.response['Error']['Code'].should.equal('AlreadyExistsException')
|
||||||
|
|
||||||
|
|
||||||
@mock_glue
|
@mock_glue
|
||||||
@ -87,11 +93,7 @@ def test_get_tables():
|
|||||||
table_inputs = {}
|
table_inputs = {}
|
||||||
|
|
||||||
for table_name in table_names:
|
for table_name in table_names:
|
||||||
s3_location = 's3://my-bucket/{database_name}/{table_name}'.format(
|
table_input = helpers.create_table_input(database_name, table_name)
|
||||||
database_name=database_name,
|
|
||||||
table_name=table_name
|
|
||||||
)
|
|
||||||
table_input = helpers.create_table_input(table_name, s3_location)
|
|
||||||
table_inputs[table_name] = table_input
|
table_inputs[table_name] = table_input
|
||||||
helpers.create_table(client, database_name, table_name, table_input)
|
helpers.create_table(client, database_name, table_name, table_input)
|
||||||
|
|
||||||
@ -99,10 +101,326 @@ def test_get_tables():
|
|||||||
|
|
||||||
tables = response['TableList']
|
tables = response['TableList']
|
||||||
|
|
||||||
assert len(tables) == 3
|
tables.should.have.length_of(3)
|
||||||
|
|
||||||
for table in tables:
|
for table in tables:
|
||||||
table_name = table['Name']
|
table_name = table['Name']
|
||||||
table_name.should.equal(table_inputs[table_name]['Name'])
|
table_name.should.equal(table_inputs[table_name]['Name'])
|
||||||
table['StorageDescriptor'].should.equal(table_inputs[table_name]['StorageDescriptor'])
|
table['StorageDescriptor'].should.equal(table_inputs[table_name]['StorageDescriptor'])
|
||||||
table['PartitionKeys'].should.equal(table_inputs[table_name]['PartitionKeys'])
|
table['PartitionKeys'].should.equal(table_inputs[table_name]['PartitionKeys'])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_table_versions():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
version_inputs = {}
|
||||||
|
|
||||||
|
table_input = helpers.create_table_input(database_name, table_name)
|
||||||
|
helpers.create_table(client, database_name, table_name, table_input)
|
||||||
|
version_inputs["1"] = table_input
|
||||||
|
|
||||||
|
columns = [{'Name': 'country', 'Type': 'string'}]
|
||||||
|
table_input = helpers.create_table_input(database_name, table_name, columns=columns)
|
||||||
|
helpers.update_table(client, database_name, table_name, table_input)
|
||||||
|
version_inputs["2"] = table_input
|
||||||
|
|
||||||
|
# Updateing with an indentical input should still create a new version
|
||||||
|
helpers.update_table(client, database_name, table_name, table_input)
|
||||||
|
version_inputs["3"] = table_input
|
||||||
|
|
||||||
|
response = helpers.get_table_versions(client, database_name, table_name)
|
||||||
|
|
||||||
|
vers = response['TableVersions']
|
||||||
|
|
||||||
|
vers.should.have.length_of(3)
|
||||||
|
vers[0]['Table']['StorageDescriptor']['Columns'].should.equal([])
|
||||||
|
vers[-1]['Table']['StorageDescriptor']['Columns'].should.equal(columns)
|
||||||
|
|
||||||
|
for n, ver in enumerate(vers):
|
||||||
|
n = str(n + 1)
|
||||||
|
ver['VersionId'].should.equal(n)
|
||||||
|
ver['Table']['Name'].should.equal(table_name)
|
||||||
|
ver['Table']['StorageDescriptor'].should.equal(version_inputs[n]['StorageDescriptor'])
|
||||||
|
ver['Table']['PartitionKeys'].should.equal(version_inputs[n]['PartitionKeys'])
|
||||||
|
|
||||||
|
response = helpers.get_table_version(client, database_name, table_name, "3")
|
||||||
|
ver = response['TableVersion']
|
||||||
|
|
||||||
|
ver['VersionId'].should.equal("3")
|
||||||
|
ver['Table']['Name'].should.equal(table_name)
|
||||||
|
ver['Table']['StorageDescriptor']['Columns'].should.equal(columns)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_table_version_not_found():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_table_version(client, database_name, 'myfirsttable', "20")
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('version', re.I)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_table_version_invalid_input():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_table_version(client, database_name, 'myfirsttable', "10not-an-int")
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('InvalidInputException')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_table_not_exits():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_table(client, database_name, 'myfirsttable')
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('Table myfirsttable not found')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_table_when_database_not_exits():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'nosuchdatabase'
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_table(client, database_name, 'myfirsttable')
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('Database nosuchdatabase not found')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_partitions_empty():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
response = client.get_partitions(DatabaseName=database_name, TableName=table_name)
|
||||||
|
|
||||||
|
response['Partitions'].should.have.length_of(0)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_partition():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
values = ['2018-10-01']
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
before = datetime.now(pytz.utc)
|
||||||
|
|
||||||
|
part_input = helpers.create_partition_input(database_name, table_name, values=values)
|
||||||
|
helpers.create_partition(client, database_name, table_name, part_input)
|
||||||
|
|
||||||
|
after = datetime.now(pytz.utc)
|
||||||
|
|
||||||
|
response = client.get_partitions(DatabaseName=database_name, TableName=table_name)
|
||||||
|
|
||||||
|
partitions = response['Partitions']
|
||||||
|
|
||||||
|
partitions.should.have.length_of(1)
|
||||||
|
|
||||||
|
partition = partitions[0]
|
||||||
|
|
||||||
|
partition['TableName'].should.equal(table_name)
|
||||||
|
partition['StorageDescriptor'].should.equal(part_input['StorageDescriptor'])
|
||||||
|
partition['Values'].should.equal(values)
|
||||||
|
partition['CreationTime'].should.be.greater_than(before)
|
||||||
|
partition['CreationTime'].should.be.lower_than(after)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_create_partition_already_exist():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
values = ['2018-10-01']
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values)
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('AlreadyExistsException')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_partition_not_found():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
values = ['2018-10-01']
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_partition(client, database_name, table_name, values)
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('partition')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_get_partition():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
values = [['2018-10-01'], ['2018-09-01']]
|
||||||
|
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values[0])
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values[1])
|
||||||
|
|
||||||
|
response = client.get_partition(DatabaseName=database_name, TableName=table_name, PartitionValues=values[1])
|
||||||
|
|
||||||
|
partition = response['Partition']
|
||||||
|
|
||||||
|
partition['TableName'].should.equal(table_name)
|
||||||
|
partition['Values'].should.equal(values[1])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_update_partition_not_found_moving():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.update_partition(client, database_name, table_name, old_values=['0000-00-00'], values=['2018-10-02'])
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('partition')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_update_partition_not_found_change_in_place():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
values = ['2018-10-01']
|
||||||
|
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.update_partition(client, database_name, table_name, old_values=values, values=values)
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
exc.exception.response['Error']['Message'].should.match('partition')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_update_partition_cannot_overwrite():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
|
||||||
|
values = [['2018-10-01'], ['2018-09-01']]
|
||||||
|
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values[0])
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values[1])
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.update_partition(client, database_name, table_name, old_values=values[0], values=values[1])
|
||||||
|
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('AlreadyExistsException')
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_update_partition():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
values = ['2018-10-01']
|
||||||
|
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values)
|
||||||
|
|
||||||
|
response = helpers.update_partition(
|
||||||
|
client,
|
||||||
|
database_name,
|
||||||
|
table_name,
|
||||||
|
old_values=values,
|
||||||
|
values=values,
|
||||||
|
columns=[{'Name': 'country', 'Type': 'string'}],
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.get_partition(DatabaseName=database_name, TableName=table_name, PartitionValues=values)
|
||||||
|
partition = response['Partition']
|
||||||
|
|
||||||
|
partition['TableName'].should.equal(table_name)
|
||||||
|
partition['StorageDescriptor']['Columns'].should.equal([{'Name': 'country', 'Type': 'string'}])
|
||||||
|
|
||||||
|
|
||||||
|
@mock_glue
|
||||||
|
def test_update_partition_move():
|
||||||
|
client = boto3.client('glue', region_name='us-east-1')
|
||||||
|
database_name = 'myspecialdatabase'
|
||||||
|
table_name = 'myfirsttable'
|
||||||
|
values = ['2018-10-01']
|
||||||
|
new_values = ['2018-09-01']
|
||||||
|
|
||||||
|
helpers.create_database(client, database_name)
|
||||||
|
helpers.create_table(client, database_name, table_name)
|
||||||
|
helpers.create_partition(client, database_name, table_name, values=values)
|
||||||
|
|
||||||
|
response = helpers.update_partition(
|
||||||
|
client,
|
||||||
|
database_name,
|
||||||
|
table_name,
|
||||||
|
old_values=values,
|
||||||
|
values=new_values,
|
||||||
|
columns=[{'Name': 'country', 'Type': 'string'}],
|
||||||
|
)
|
||||||
|
|
||||||
|
with assert_raises(ClientError) as exc:
|
||||||
|
helpers.get_partition(client, database_name, table_name, values)
|
||||||
|
|
||||||
|
# Old partition shouldn't exist anymore
|
||||||
|
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||||
|
|
||||||
|
response = client.get_partition(DatabaseName=database_name, TableName=table_name, PartitionValues=new_values)
|
||||||
|
partition = response['Partition']
|
||||||
|
|
||||||
|
partition['TableName'].should.equal(table_name)
|
||||||
|
partition['StorageDescriptor']['Columns'].should.equal([{'Name': 'country', 'Type': 'string'}])
|
||||||
|
Loading…
Reference in New Issue
Block a user