A CRUD wrapper class for Amazon DynamoDB
Project description
# cruddy
[![Circle CI](https://circleci.com/gh/cloudnative/cruddy.svg?style=svg)](https://circleci.com/gh/cloudnative/cruddy)
A simple CRUD wrapper around Amazon DynamoDB.
## Installation
```
$ pip install cruddy
```
## Getting Started
The first thing to do is to create a CRUD handler for your DynamoDB table. The
constructor for the CRUD class takes a number of parameters to help configure
the handler for your application. The full list of parameters are:
* table_name - name of the backing DynamoDB table (required)
* profile_name - name of the AWS credential profile to use when creating the
boto3 Session
* region_name - name of the AWS region to use when creating the boto3 Session
* prototype - a dictionary that describes the prototypical object stored in
your table (see below)
* supported_ops - a list of operations supported by the CRUD handler
(choices are list, get, create, update, delete, query)
* encrypted_attributes - a list of tuples where the first item in the tuple is
the name of the attribute that should be encrypted and the second
item in the tuple is the KMS master key ID to use for
encrypting/decrypting the value.
* debug - if not False this will cause the raw_response to be left
in the response dictionary
### Prototypes
A prototype is a description of the prototypical item in your table. It's
kind of like a template for the item. A prototype can be used to describe what
attributes are in the item, which are required or optional, and the type of
value that is associated with the attribute. In addition, there are special
values you can use that allow a small range of calculated values in your item.
If you don't specify a prototype, cruddy will store whatever values are in the
item with no validation or insertion of calculated values.
Let's look at a few examples using prototypes.
```
{
'id': str,
'created_at': int,
'foo': int
}
```
This prototype says that your item must have an ``id`` attribute whose value is
of type ``str``, a ``created_at`` attribute whose value is of type ``int``, and
a ``foo`` attribute whose value is also an ``int``. Your item may contain
other items as well (this is not a schema) but it must contain these attribute
name/value pairs. If the item you pass into the ``create`` method does not
contain these attributes cruddy will create the necessary attributes and will
initialize the value to what ever value is created by calling the specified
Python type (e.g. int() returns 0, str() returns '').
#### Defaults
We could also define a prototype like this:
```
{
'id': str,
'created_at': int,
'foo': 1
}
```
Note that the ``foo`` attribute now has a value of ``1`` rather than referring
to a Python type. This is used as the default value rather than the default
value associated with Python type. So, in this case if you pass an
item to the ``create`` method that does have a value for the ``foo`` attribute
cruddy will create an attribute called ``foo`` with a value of ``1``. In
addition, when the item is later updated cruddy will make sure that the value
of the attribute ``foo`` is of the correct type (``int``) but the default value
will only be used at create time.
#### Calculated Values
The above example assumes that you are going to generate the ``id`` and
``created_at`` values in your application code. You may, however, prefer to
have cruddy handle that for you. In that case, you can make use of cruddy's
calcuated value tokens.
```
{
'id': 'on-create:<uuid>',
'created_at': 'on-create:<timestamp>'
}
```
Now, when you create a new item you could supply one without an ``id`` or
``created_at`` value and cruddy will calculate these values for you. If those
attributes already exist in the item, cruddy will not overwrite them. Note
that the calulated values are specified as ``on-create``. This is called a
``trigger`` and indicates when the calculation will be performed.
If you wanted to also have a timestamp to indicate when an item has been
modified (i.e. created or updated) you could do this.
```
{
'id': 'on-create:<uuid>',
'created_at': 'on-create:<timestamp>',
'modified_at': 'on-update:<timestamp>'
}
```
The currently supported calculated value types are:
* **<uuid>** to generate a string representation of a Type4 UUID
* **<timestamp>** to generate an integer timestamp generated by
``int(time.time()*1000)``
The currently supported triggers for calculated values are:
* **on-create** will be applied when the item is created
* **on-update** will be applied when the item is created or updated
### Configuring your CRUD handler
An easy way to configure your CRUD handler is to gather all of the parameters
together in a dictionary and then pass that dictionary to the class
constructor.
```
import cruddy
params = {
'profile_name': 'foobar',
'region_name': 'us-west-2',
'table_name': 'fiebaz',
'prototype': {'id': '<on-create:uuid>',
'created_at': '<on-create:timestamp>',
'modified_at': '<on-update:timestamp>'}
}
crud = cruddy.CRUD(**params)
```
Once you have your handler, you can start to use it.
```
item = {'name': 'the dude', 'email': 'the@dude.com', 'twitter': 'thedude'}
response = crud.create(item)
```
The response returned from all CRUD operations is a Python object with the
following attributes.
* **data** is the actual data returned from the CRUD operation (if successful)
* **status** is the status of the response and is either ``success`` or
``error``
* **metadata** is metadata from the underlying DynamoDB API call
* **error_type** will be the type of error, if ``status != 'success'``
* **error_code** will be the code of error, if ``status != 'success'``
* **error_type** will be the full error message, if ``status != 'success'``
* **raw_response** will contain the full response from DynamoDB if the CRUD
handler is in ``debug`` mode.
* **is_successful** a simple short-cut, equivalent to ``status == 'success'``
You can convert the CRUDResponse object into a standard Python dictionary using
the ``flatten`` method
```
>>> response = crud.create(...)
>>> response.flatten()
{'data': {'created_at': 1452109758363,
'name': 'the dude',
'email': 'the@dude.com',
'twitter': 'thedude',
'id': 'a6ac0fd7-cdde-4170-a1a9-30e139c44897',
'modified_at': 1452109758363},
'error_code': None,
'error_message': None,
'error_type': None,
'metadata': {'HTTPStatusCode': 200,
'RequestId': 'LBBFLMIAVOKR8LOTK7SRGFO4Q3VV4KQNSO5AEMVJF66Q9ASUAAJG'},
'raw_response': None,
'status': 'success'}
>>>
```
## CRUD operations
The CRUD object supports the following operations. Note that depending on the
value of the ``supported_operations`` parameter passed to the constructor, some
of these methods may return an ``UnsupportedOperation`` error type.
### list()
Returns a list of items in the database. Encrypted attributes are not
decrypted when listing items.
### get(*id*, *decrypt=False*)
Returns the item corresponding to ``id``. If the ``decrypt`` param is not
False (the default) any encrypted attributes in the item will be decrypted
before the item is returned. If not, the encrypted attributes will contain the
encrypted value.
### create(*item*)
Creates a new item. You pass in an item containing initial values. Any
attribute names defined in ``defaults`` that are missing from the item will be
added using the default value defined in ``defaults``.
If you supplied a dictionary like this for ``defaults`` when you created your
CRUD handler:
```
defaults = {
'foo': 'bar',
'fie': 'baz
}
```
And then you created a new item like this:
```
item = {'foo': 'bar'}
response = crud.create(item)
```
The newly created item would look like this:
```
{
'foo': 'bar',
'fie': 'baz',
'id': '<uuid>'
}
```
The default value in the ``defaults`` dictionary can be any value that can be
encoded into a JSON object.
In addition to normal constant values you can also use several special values
that allow you to insert dynamic default values in newly created objects. The
currently supported special values are:
* ``<uuid>`` which will be replaced by a new Type 4 UUID string
* ``<timestamp>`` which will be replaced by a timestamp value generated by
taking ``time.time()``, multiplying by 1000 and then converting to an ``int``.
So, if you had a ``defaults`` dictionary like this:
```
defaults = {
'foo': 'bar',
'fie': '<uuid>',
'baz': '<timestamp>'
}
```
The new object we created would look something like this:
```
{
'foo': 'bar',
'fie': 'd74d766d-0912-4c0a-aabd-7c1819971ea3',
'id': 'a7f9aac9-ab08-4b2d-8d52-961025d01c63',
'created_at': 1452307198410,
'modified_at': 1452307198410,
'baz': 1452307198501
}
```
Note that cruddy always inserts ``id``, ``created_at``, and ``modified_at``
into the ``defaults`` dictionary you pass in.
### update(*item*)
Updates the item based on the current values of the dictionary passed in.
### delete(*id*)
Deletes the item corresponding to ``id``.
### query(*query*)
Query isn't really a CRUD operation but it is pretty useful. Cruddy provides a
limited but useful interface to query GSI indexes in DynamoDB with the following
limitations (hopefully some of these will be expanded or eliminated in the
future.
* The GSI must be configured with a only HASH and not a RANGE.
* The only operation supported in the query is equality
To use the ``query`` operation you must pass in a query string of this form:
<attribute_name>=<value>
As stated above, the only operation currently supported is equality (=) but
other operations will be added over time. Also, the ``attribute_name`` must be
an attribute which is configured as the ``HASH`` of a GSI in the DynamoDB
table. If all of the above conditions are met, the ``query`` operation will
return a list (possibly empty) of all items matching the query and the
``status`` of the response will be ``success``. Otherwise, the ``status`` will
be ``error`` and the ``error_type`` and ``error_message`` will provide further
information about the error.
[![Circle CI](https://circleci.com/gh/cloudnative/cruddy.svg?style=svg)](https://circleci.com/gh/cloudnative/cruddy)
A simple CRUD wrapper around Amazon DynamoDB.
## Installation
```
$ pip install cruddy
```
## Getting Started
The first thing to do is to create a CRUD handler for your DynamoDB table. The
constructor for the CRUD class takes a number of parameters to help configure
the handler for your application. The full list of parameters are:
* table_name - name of the backing DynamoDB table (required)
* profile_name - name of the AWS credential profile to use when creating the
boto3 Session
* region_name - name of the AWS region to use when creating the boto3 Session
* prototype - a dictionary that describes the prototypical object stored in
your table (see below)
* supported_ops - a list of operations supported by the CRUD handler
(choices are list, get, create, update, delete, query)
* encrypted_attributes - a list of tuples where the first item in the tuple is
the name of the attribute that should be encrypted and the second
item in the tuple is the KMS master key ID to use for
encrypting/decrypting the value.
* debug - if not False this will cause the raw_response to be left
in the response dictionary
### Prototypes
A prototype is a description of the prototypical item in your table. It's
kind of like a template for the item. A prototype can be used to describe what
attributes are in the item, which are required or optional, and the type of
value that is associated with the attribute. In addition, there are special
values you can use that allow a small range of calculated values in your item.
If you don't specify a prototype, cruddy will store whatever values are in the
item with no validation or insertion of calculated values.
Let's look at a few examples using prototypes.
```
{
'id': str,
'created_at': int,
'foo': int
}
```
This prototype says that your item must have an ``id`` attribute whose value is
of type ``str``, a ``created_at`` attribute whose value is of type ``int``, and
a ``foo`` attribute whose value is also an ``int``. Your item may contain
other items as well (this is not a schema) but it must contain these attribute
name/value pairs. If the item you pass into the ``create`` method does not
contain these attributes cruddy will create the necessary attributes and will
initialize the value to what ever value is created by calling the specified
Python type (e.g. int() returns 0, str() returns '').
#### Defaults
We could also define a prototype like this:
```
{
'id': str,
'created_at': int,
'foo': 1
}
```
Note that the ``foo`` attribute now has a value of ``1`` rather than referring
to a Python type. This is used as the default value rather than the default
value associated with Python type. So, in this case if you pass an
item to the ``create`` method that does have a value for the ``foo`` attribute
cruddy will create an attribute called ``foo`` with a value of ``1``. In
addition, when the item is later updated cruddy will make sure that the value
of the attribute ``foo`` is of the correct type (``int``) but the default value
will only be used at create time.
#### Calculated Values
The above example assumes that you are going to generate the ``id`` and
``created_at`` values in your application code. You may, however, prefer to
have cruddy handle that for you. In that case, you can make use of cruddy's
calcuated value tokens.
```
{
'id': 'on-create:<uuid>',
'created_at': 'on-create:<timestamp>'
}
```
Now, when you create a new item you could supply one without an ``id`` or
``created_at`` value and cruddy will calculate these values for you. If those
attributes already exist in the item, cruddy will not overwrite them. Note
that the calulated values are specified as ``on-create``. This is called a
``trigger`` and indicates when the calculation will be performed.
If you wanted to also have a timestamp to indicate when an item has been
modified (i.e. created or updated) you could do this.
```
{
'id': 'on-create:<uuid>',
'created_at': 'on-create:<timestamp>',
'modified_at': 'on-update:<timestamp>'
}
```
The currently supported calculated value types are:
* **<uuid>** to generate a string representation of a Type4 UUID
* **<timestamp>** to generate an integer timestamp generated by
``int(time.time()*1000)``
The currently supported triggers for calculated values are:
* **on-create** will be applied when the item is created
* **on-update** will be applied when the item is created or updated
### Configuring your CRUD handler
An easy way to configure your CRUD handler is to gather all of the parameters
together in a dictionary and then pass that dictionary to the class
constructor.
```
import cruddy
params = {
'profile_name': 'foobar',
'region_name': 'us-west-2',
'table_name': 'fiebaz',
'prototype': {'id': '<on-create:uuid>',
'created_at': '<on-create:timestamp>',
'modified_at': '<on-update:timestamp>'}
}
crud = cruddy.CRUD(**params)
```
Once you have your handler, you can start to use it.
```
item = {'name': 'the dude', 'email': 'the@dude.com', 'twitter': 'thedude'}
response = crud.create(item)
```
The response returned from all CRUD operations is a Python object with the
following attributes.
* **data** is the actual data returned from the CRUD operation (if successful)
* **status** is the status of the response and is either ``success`` or
``error``
* **metadata** is metadata from the underlying DynamoDB API call
* **error_type** will be the type of error, if ``status != 'success'``
* **error_code** will be the code of error, if ``status != 'success'``
* **error_type** will be the full error message, if ``status != 'success'``
* **raw_response** will contain the full response from DynamoDB if the CRUD
handler is in ``debug`` mode.
* **is_successful** a simple short-cut, equivalent to ``status == 'success'``
You can convert the CRUDResponse object into a standard Python dictionary using
the ``flatten`` method
```
>>> response = crud.create(...)
>>> response.flatten()
{'data': {'created_at': 1452109758363,
'name': 'the dude',
'email': 'the@dude.com',
'twitter': 'thedude',
'id': 'a6ac0fd7-cdde-4170-a1a9-30e139c44897',
'modified_at': 1452109758363},
'error_code': None,
'error_message': None,
'error_type': None,
'metadata': {'HTTPStatusCode': 200,
'RequestId': 'LBBFLMIAVOKR8LOTK7SRGFO4Q3VV4KQNSO5AEMVJF66Q9ASUAAJG'},
'raw_response': None,
'status': 'success'}
>>>
```
## CRUD operations
The CRUD object supports the following operations. Note that depending on the
value of the ``supported_operations`` parameter passed to the constructor, some
of these methods may return an ``UnsupportedOperation`` error type.
### list()
Returns a list of items in the database. Encrypted attributes are not
decrypted when listing items.
### get(*id*, *decrypt=False*)
Returns the item corresponding to ``id``. If the ``decrypt`` param is not
False (the default) any encrypted attributes in the item will be decrypted
before the item is returned. If not, the encrypted attributes will contain the
encrypted value.
### create(*item*)
Creates a new item. You pass in an item containing initial values. Any
attribute names defined in ``defaults`` that are missing from the item will be
added using the default value defined in ``defaults``.
If you supplied a dictionary like this for ``defaults`` when you created your
CRUD handler:
```
defaults = {
'foo': 'bar',
'fie': 'baz
}
```
And then you created a new item like this:
```
item = {'foo': 'bar'}
response = crud.create(item)
```
The newly created item would look like this:
```
{
'foo': 'bar',
'fie': 'baz',
'id': '<uuid>'
}
```
The default value in the ``defaults`` dictionary can be any value that can be
encoded into a JSON object.
In addition to normal constant values you can also use several special values
that allow you to insert dynamic default values in newly created objects. The
currently supported special values are:
* ``<uuid>`` which will be replaced by a new Type 4 UUID string
* ``<timestamp>`` which will be replaced by a timestamp value generated by
taking ``time.time()``, multiplying by 1000 and then converting to an ``int``.
So, if you had a ``defaults`` dictionary like this:
```
defaults = {
'foo': 'bar',
'fie': '<uuid>',
'baz': '<timestamp>'
}
```
The new object we created would look something like this:
```
{
'foo': 'bar',
'fie': 'd74d766d-0912-4c0a-aabd-7c1819971ea3',
'id': 'a7f9aac9-ab08-4b2d-8d52-961025d01c63',
'created_at': 1452307198410,
'modified_at': 1452307198410,
'baz': 1452307198501
}
```
Note that cruddy always inserts ``id``, ``created_at``, and ``modified_at``
into the ``defaults`` dictionary you pass in.
### update(*item*)
Updates the item based on the current values of the dictionary passed in.
### delete(*id*)
Deletes the item corresponding to ``id``.
### query(*query*)
Query isn't really a CRUD operation but it is pretty useful. Cruddy provides a
limited but useful interface to query GSI indexes in DynamoDB with the following
limitations (hopefully some of these will be expanded or eliminated in the
future.
* The GSI must be configured with a only HASH and not a RANGE.
* The only operation supported in the query is equality
To use the ``query`` operation you must pass in a query string of this form:
<attribute_name>=<value>
As stated above, the only operation currently supported is equality (=) but
other operations will be added over time. Also, the ``attribute_name`` must be
an attribute which is configured as the ``HASH`` of a GSI in the DynamoDB
table. If all of the above conditions are met, the ``query`` operation will
return a list (possibly empty) of all items matching the query and the
``status`` of the response will be ``success``. Otherwise, the ``status`` will
be ``error`` and the ``error_type`` and ``error_message`` will provide further
information about the error.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
cruddy-0.4.1.tar.gz
(13.4 kB
view details)
File details
Details for the file cruddy-0.4.1.tar.gz
.
File metadata
- Download URL: cruddy-0.4.1.tar.gz
- Upload date:
- Size: 13.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 49deb91340f7a388f5140223b2b7da2e2ae9047844c3f6b395076a8606547051 |
|
MD5 | 3f5f9d766023f06c4bd30eb9904c5e16 |
|
BLAKE2b-256 | 48a18ebe4a77598dafed2b3c5e2943f2d186caec3361593a03cc1e5810b888b1 |