├── .github
└── workflows
│ └── pypi-deploy.yml
├── .gitignore
├── Dockerfile
├── LICENSE
├── LucidDynamodb
├── __init__.py
├── docs.md
├── exceptions.py
├── operations.py
└── utils.py
├── README.md
├── deploy.sh
├── examples
├── 1-create-a-new-table.py
├── 10-increase-an-existing-attribute-value.py
├── 11-delete-an-attribute-from-an-item.py
├── 12-delete-an-attribute-from-the-string-set.py
├── 13-delete-an-item.py
├── 14-delete-a-table.py
├── 2-get-all-table-names.py
├── 3-create-a-new-item.py
├── 4-read-an-item.py
├── 5-read-items-by-filter.py
├── 6-update-existing-attribute-in-an-item.py
├── 7-add-a-new-attribute-in-an-item.py
├── 8-add-an-attribute-to-the-list.py
├── 9-add-an-attribute-to-the-string-set.py
├── using-aws-config-to-connect-to-dynamodb.py
└── using-aws-secret-to-connect-to-dynamodb.py
├── helm
├── Chart.yaml
├── templates
│ └── lucid-docs
│ │ ├── deployment.yaml
│ │ ├── ingress.yaml
│ │ └── service.yaml
└── values.yaml
├── package.json
├── requirements-dev.txt
├── requirements.txt
├── server.js
├── setup.py
├── sonar-project.properties
└── tests
├── __init__.py
└── test_crud.py
/.github/workflows/pypi-deploy.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**' # matches every branch
7 |
8 | jobs:
9 |
10 | tests:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
16 |
17 | - name: Start integration test
18 | env:
19 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
20 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
21 | run: |
22 | pip3 install virtualenv
23 | python3 -m venv env
24 | source env/bin/activate
25 | pip3 install -r requirements-dev.txt
26 | python setup.py install
27 | pytest tests --doctest-modules --junitxml=junit/test-results.xml --cov=. --cov-report=xml
28 |
29 | - name: SonarCloud Scan
30 | uses: sonarsource/sonarcloud-github-action@master
31 | env:
32 | GITHUB_TOKEN: ${{ github.token }}
33 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
34 |
35 | deploy-to-pypi:
36 | needs: tests
37 | runs-on: ubuntu-latest
38 | if: github.ref == 'refs/heads/master'
39 | steps:
40 | - uses: actions/checkout@v2
41 | - name: Install package dependencies
42 | run: pip3 install -r requirements-dev.txt
43 |
44 | - name: Build python package and deploy to PYPI
45 | run: |
46 | rm -rf build dist *.egg-info
47 | python3 setup.py sdist bdist_wheel
48 | twine upload --username ${{ secrets.PYPI_USERNAME }} --password ${{ secrets.PYPI_PASSWORD }} --skip-existing dist/*
49 |
50 | upload-docs-docker-image:
51 | needs: deploy-to-pypi
52 | runs-on: ubuntu-latest
53 | if: github.event_name == 'push'
54 | steps:
55 | - uses: actions/checkout@v2
56 |
57 | - name: Build pdoc docs image
58 | run: |
59 | sudo pip3 install pdoc3
60 | sudo pip3 install -r requirements-dev.txt
61 | sudo pdoc --html ./LucidDynamodb --output-dir ./docs --force
62 | docker build --no-cache -t dineshsonachalam/lucid-dynamodb-docs:latest .
63 |
64 | - name: Log into registry
65 | run: docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
66 |
67 | - name: Push image
68 | run: |
69 | docker push dineshsonachalam/lucid-dynamodb-docs:latest
70 |
71 | deploy-to-k8-cluster:
72 | needs: upload-docs-docker-image
73 | runs-on: ubuntu-latest
74 | steps:
75 | - uses: actions/checkout@master
76 |
77 | - uses: danielr1996/kubectl-action@1.0.0
78 | name: Docs deployment rolling restart to fetch recently build docker image from docker hub.
79 | with:
80 | kubeconfig: ${{ secrets.KUBE_CONFIG_DATA }}
81 | args: rollout restart deployment lucid-docs -n=dinesh
82 |
83 | - uses: danielr1996/kubectl-action@1.0.0
84 | name: Verify deployment for docs app
85 | with:
86 | kubeconfig: ${{ secrets.KUBE_CONFIG_DATA }}
87 | args: rollout status deployment/lucid-docs -n=dinesh
88 |
89 | auto-update-readme:
90 | needs: deploy-to-k8-cluster
91 | runs-on: ubuntu-latest
92 | if: github.ref == 'refs/heads/master'
93 | steps:
94 | - uses: actions/checkout@v2
95 | - name: Markdown autodocs
96 | uses: dineshsonachalam/markdown-autodocs@v1.0.3
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | junit/
6 | node_modules/
7 | package-lock.json
8 | # C extensions
9 | *.so
10 | docs/
11 |
12 | # Distribution / packaging
13 | .Python
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | share/python-wheels/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 | MANIFEST
31 |
32 | # PyInstaller
33 | # Usually these files are written by a python script from a template
34 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
35 | *.manifest
36 | *.spec
37 |
38 | # Installer logs
39 | pip-log.txt
40 | pip-delete-this-directory.txt
41 |
42 | # Unit test / coverage reports
43 | htmlcov/
44 | .tox/
45 | .nox/
46 | .coverage
47 | .coverage.*
48 | .cache
49 | nosetests.xml
50 | coverage.xml
51 | *.cover
52 | *.py,cover
53 | .hypothesis/
54 | .pytest_cache/
55 | cover/
56 |
57 | # Translations
58 | *.mo
59 | *.pot
60 |
61 | # Django stuff:
62 | *.log
63 | local_settings.py
64 | db.sqlite3
65 | db.sqlite3-journal
66 |
67 | # Flask stuff:
68 | instance/
69 | .webassets-cache
70 |
71 | # Scrapy stuff:
72 | .scrapy
73 |
74 | # Sphinx documentation
75 | docs/_build/
76 |
77 | # PyBuilder
78 | .pybuilder/
79 | target/
80 |
81 | # Jupyter Notebook
82 | .ipynb_checkpoints
83 |
84 | # IPython
85 | profile_default/
86 | ipython_config.py
87 |
88 | # pyenv
89 | # For a library or package, you might want to ignore these files since the code is
90 | # intended to run in multiple environments; otherwise, check them in:
91 | # .python-version
92 |
93 | # pipenv
94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
97 | # install all needed dependencies.
98 | #Pipfile.lock
99 |
100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
101 | __pypackages__/
102 |
103 | # Celery stuff
104 | celerybeat-schedule
105 | celerybeat.pid
106 |
107 | # SageMath parsed files
108 | *.sage.py
109 |
110 | # Environments
111 | .env
112 | .venv
113 | env/
114 | venv/
115 | ENV/
116 | env.bak/
117 | venv.bak/
118 |
119 | # Spyder project settings
120 | .spyderproject
121 | .spyproject
122 |
123 | # Rope project settings
124 | .ropeproject
125 |
126 | # mkdocs documentation
127 | /site
128 |
129 | # mypy
130 | .mypy_cache/
131 | .dmypy.json
132 | dmypy.json
133 |
134 | # Pyre type checker
135 | .pyre/
136 |
137 | # pytype static type analyzer
138 | .pytype/
139 |
140 | # Cython debug symbols
141 | cython_debug/
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.6.0-slim
2 |
3 | # Create app directory
4 | WORKDIR /usr/src/app
5 |
6 | # Install app dependencies
7 | RUN npm init -y
8 | RUN npm i -s express
9 |
10 | COPY docs .
11 | COPY server.js .
12 |
13 | EXPOSE 3000
14 | CMD [ "node", "server.js" ]
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Dinesh Sonachalam
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.
--------------------------------------------------------------------------------
/LucidDynamodb/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Python package `LucidDynamodb` provides a pythonic interface to Amazon’s DynamoDB. It uses simple, powerful abstractions over the DynamoDB API. LucidDynamodb allows you to start developing immediately.
3 |
4 | .. include:: ./docs.md
5 | """
6 | from LucidDynamodb.operations import *
--------------------------------------------------------------------------------
/LucidDynamodb/docs.md:
--------------------------------------------------------------------------------
1 | ## Features
2 |
3 | * Python 3 support
4 | * Support for all of the DynamoDB API
5 | * Support for Global and Local Secondary Indexes
6 | * Fully tested
7 |
8 | Ask questions in the GitHub issues
9 |
10 | ## Table of contents
11 | - [Installation](#installation)
12 | - [Example](#example)
13 | - [Connect to DynamodDB](#connect-to-dynamodb)
14 | - [Create a new table](#create-a-new-table)
15 | - [Get all table names](#get-all-table-names)
16 | - [Create a New Item](#create-a-new-item)
17 | - [Read an Item](#read-an-item)
18 | - [Read items by filter](#read-items-by-filter)
19 | - [Update existing attribute in an item](#update-existing-attribute-in-an-item)
20 | - [Add a new attribute in an item](#add-a-new-attribute-in-an-item)
21 | - [Add an attribute to the list](#add-an-attribute-to-the-list)
22 | - [Add an attribute to the string set](#add-an-attribute-to-the-string-set)
23 | - [Increase an existing attribute value](#increase-an-existing-attribute-value)
24 | - [Delete an attribute from an item](#delete-an-attribute-from-an-item)
25 | - [Delete an attribute from the string set](#delete-an-attribute-from-the-string-set)
26 | - [Delete an item](#delete-an-item)
27 | - [Delete a table](#delete-a-table)
28 | - [Running tests](#running-tests)
29 | - [Github Workflow Artifacts](#github-workflow-artifacts)
30 | - [License](#license)
31 |
32 | ## Installation
33 |
34 |
35 | ```console
36 | pip install LucidDynamodb
37 | ```
38 |
39 |
40 |
41 | **Note:** Prerequisite for Python3 development
42 |
43 | ## Example
44 |
45 | #### Connect to DynamoDB
46 | You can connect to DynamoDB by following any of these two ways.
47 |
48 | 1. Using AWS config
49 |
50 |
51 | ```py
52 | from LucidDynamodb import DynamoDb
53 | db = DynamoDb()
54 |
55 | """
56 | $ pip install awscli #can add user flag
57 | $ aws configure
58 | AWS Access Key ID [****************ABCD]:[enter your key here]
59 | AWS Secret Access Key [****************xyz]:[enter your secret key here]
60 | Default region name [us-west-2]:[enter your region here]
61 | Default output format [None]:
62 | """
63 | ```
64 |
65 |
66 | 2. Using AWS secret key
67 |
68 |
69 | ```py
70 | from LucidDynamodb import DynamoDb
71 | import os
72 | AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
73 | AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
74 | db = DynamoDb(region_name="us-east-1",
75 | aws_access_key_id=AWS_ACCESS_KEY_ID,
76 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
77 | ```
78 |
79 |
80 | #### Create a new table
81 |
82 |
83 | ```py
84 | from LucidDynamodb import DynamoDb
85 | from LucidDynamodb.exceptions import (
86 | TableAlreadyExists
87 | )
88 | import logging
89 | logging.basicConfig(level=logging.INFO)
90 |
91 | table_schema = {
92 | "TableName": "dev_jobs",
93 | "KeySchema": [{
94 | "AttributeName": "company_name",
95 | "KeyType": "HASH"
96 | },
97 | {
98 | "AttributeName": "role_id",
99 | "KeyType": "RANGE"
100 | }
101 | ],
102 | "AttributeDefinitions": [{
103 | "AttributeName": "company_name",
104 | "AttributeType": "S"
105 | },
106 | {
107 | "AttributeName": "role_id",
108 | "AttributeType": "S"
109 | }
110 | ],
111 | "GlobalSecondaryIndexes": [],
112 | "ProvisionedThroughput": {
113 | "ReadCapacityUnits": 1,
114 | "WriteCapacityUnits": 1
115 | }
116 | }
117 |
118 | if __name__ == "__main__":
119 | try:
120 | db = DynamoDb()
121 | db.create_table(
122 | table_name=table_schema.get("TableName"),
123 | key_schema=table_schema.get("KeySchema"),
124 | attribute_definitions=table_schema.get("AttributeDefinitions"),
125 | global_secondary_indexes=table_schema.get("GlobalSecondaryIndexes"),
126 | provisioned_throughput=table_schema.get("ProvisionedThroughput")
127 | )
128 | logging.info(f"{table_schema.get('TableName')} table created successfully")
129 | except TableAlreadyExists as e:
130 | logging.error(f"{table_schema.get('TableName')} table creation failed - {e}")
131 |
132 | """
133 | dineshsonachalam@macbook examples % python 1-create-a-new-table.py
134 | INFO:botocore.credentials:Found credentials in environment variables.
135 | INFO:root:dev_jobs table created successfully
136 | """
137 | ```
138 |
139 |
140 | #### Get all table names
141 |
142 |
143 | ```py
144 | from LucidDynamodb import DynamoDb
145 | from LucidDynamodb.exceptions import (
146 | UnexpectedError
147 | )
148 | import logging
149 | logging.basicConfig(level=logging.INFO)
150 |
151 | if __name__ == "__main__":
152 | try:
153 | db = DynamoDb()
154 | table_names = db.read_all_table_names()
155 | logging.info(f"Table names: {table_names}")
156 | except UnexpectedError as e:
157 | logging.error(f"Read all table names failed - {e}")
158 |
159 | """
160 | dineshsonachalam@macbook examples % python 2-get-all-table-names.py
161 | INFO:botocore.credentials:Found credentials in environment variables.
162 | INFO:root:Table names: ['CertMagic', 'dev_jobs', 'dev_test', 'kp-config-v1', 'test-1']
163 | """
164 | ```
165 |
166 |
167 | #### Create a new item
168 |
169 |
170 | ```py
171 | from LucidDynamodb import DynamoDb
172 | from LucidDynamodb.exceptions import (
173 | UnexpectedError
174 | )
175 | import logging
176 | logging.basicConfig(level=logging.INFO)
177 |
178 | if __name__ == "__main__":
179 | try:
180 | db = DynamoDb()
181 | db.create_item(
182 | table_name="dev_jobs",
183 | item={
184 | "company_name": "Google",
185 | "role_id": "111",
186 | "role": "Software Engineer 1",
187 | "salary": "$1,50,531",
188 | "locations": ["Mountain View, California", "Austin, Texas", "Chicago, IL"],
189 | "yearly_hike_percent": 8,
190 | "benefits": set(["Internet, Medical, Edu reimbursements",
191 | "Health insurance",
192 | "Travel reimbursements"
193 | ]),
194 | "overall_review":{
195 | "overall_rating" : "4/5",
196 | "compensation_and_benefits": "3.9/5"
197 | }
198 | }
199 | )
200 | logging.info("Item created successfully")
201 | except UnexpectedError as e:
202 | logging.error(f"Item creation failed - {e}")
203 |
204 | """
205 | dineshsonachalam@macbook examples % python 3-create-a-new-item.py
206 | INFO:botocore.credentials:Found credentials in environment variables.
207 | INFO:root:Item created successfully
208 | """
209 | ```
210 |
211 |
212 | #### Read an item
213 |
214 |
215 | ```py
216 | from LucidDynamodb import DynamoDb
217 | from LucidDynamodb.exceptions import (
218 | ItemNotFound
219 | )
220 | import logging
221 | logging.basicConfig(level=logging.INFO)
222 |
223 | if __name__ == "__main__":
224 | try:
225 | db = DynamoDb()
226 | item = db.read_item(
227 | table_name="dev_jobs",
228 | key={
229 | "company_name": "Google",
230 | "role_id": "111"
231 | }
232 | )
233 | logging.info(f"Item: {item}")
234 | except ItemNotFound as e:
235 | logging.error(f"Item doesn't exist - {e}")
236 |
237 | """
238 | dineshsonachalam@macbook examples % python 4-read-an-item.py
239 | INFO:botocore.credentials:Found credentials in environment variables.
240 | INFO:root:Item: {
241 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
242 | 'role_id': '111',
243 | 'overall_review': {
244 | 'compensation_and_benefits': '3.9/5',
245 | 'overall_rating': '4/5'
246 | },
247 | 'company_name': 'Google',
248 | 'role': 'Software Engineer 1',
249 | 'yearly_hike_percent': Decimal('8'),
250 | 'salary': '$1,50,531',
251 | 'benefits': {
252 | 'Travel reimbursements',
253 | 'Internet, Medical, Edu reimbursements',
254 | 'Health insurance'
255 | }
256 | }
257 | """
258 | ```
259 |
260 |
261 | #### Read items by filter
262 |
263 |
264 | ```py
265 | from LucidDynamodb import DynamoDb
266 | from LucidDynamodb.exceptions import (
267 | QueryFilterValidationFailed
268 | )
269 | import logging
270 | from boto3.dynamodb.conditions import Key
271 | logging.basicConfig(level=logging.INFO)
272 |
273 | if __name__ == "__main__":
274 | try:
275 | db = DynamoDb()
276 | db.create_item(
277 | table_name="dev_jobs",
278 | item={
279 | "company_name": "Google",
280 | "role_id": "112",
281 | "role": "Software Architect",
282 | "salary": "$4,80,000",
283 | "locations": ["Mountain View, California"],
284 | "yearly_hike_percent": 13,
285 | "benefits": set(["Internet reimbursements"]),
286 | "overall_review":{
287 | "overall_rating" : "3/5",
288 | "compensation_and_benefits": "4.2/5"
289 | }
290 | }
291 | )
292 | logging.info("Item created successfully")
293 | items = db.read_items_by_filter(
294 | table_name='dev_jobs',
295 | key_condition_expression=Key("company_name").eq("Google")
296 | )
297 | logging.info(f"Items: {items}")
298 | except QueryFilterValidationFailed as e:
299 | logging.error(f"Items doesn't exist - {e}")
300 |
301 | """
302 | dineshsonachalam@macbook examples % python 5-read-items-by-filter.py
303 | INFO:botocore.credentials:Found credentials in environment variables.
304 | INFO:root:Item created successfully
305 | INFO:root:Items: [{
306 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
307 | 'role_id': '111',
308 | 'overall_review': {
309 | 'compensation_and_benefits': '3.9/5',
310 | 'overall_rating': '4/5'
311 | },
312 | 'company_name': 'Google',
313 | 'role': 'Software Engineer 1',
314 | 'yearly_hike_percent': Decimal('8'),
315 | 'salary': '$1,50,531',
316 | 'benefits': {
317 | 'Internet, Medical, Edu reimbursements',
318 | 'Travel reimbursements',
319 | 'Health insurance'
320 | }
321 | }, {
322 | 'locations': ['Mountain View, California'],
323 | 'role_id': '112',
324 | 'overall_review': {
325 | 'compensation_and_benefits': '4.2/5',
326 | 'overall_rating': '3/5'
327 | },
328 | 'company_name': 'Google',
329 | 'role': 'Software Architect',
330 | 'yearly_hike_percent': Decimal('13'),
331 | 'salary': '$4,80,000',
332 | 'benefits': {
333 | 'Internet reimbursements'
334 | }
335 | }]
336 | """
337 | ```
338 |
339 |
340 | #### Update existing attribute in an item
341 |
342 |
343 | ```py
344 | from LucidDynamodb import DynamoDb
345 | from LucidDynamodb.exceptions import (
346 | UnexpectedError
347 | )
348 | import logging
349 | logging.basicConfig(level=logging.INFO)
350 |
351 | if __name__ == "__main__":
352 | try:
353 | db = DynamoDb()
354 | db.update_item(
355 | table_name="dev_jobs",
356 | key={
357 | "company_name": "Google",
358 | "role_id": "111"
359 | },
360 | attributes_to_update={
361 | 'role': 'Staff Software Engineer 2'
362 | }
363 | )
364 | logging.info("Update is successful")
365 | item = db.read_item(
366 | table_name="dev_jobs",
367 | key={
368 | "company_name": "Google",
369 | "role_id": "111"
370 | }
371 | )
372 | logging.info(f"Item: {item}")
373 | except UnexpectedError as e:
374 | logging.error(f"Update failed - {e}")
375 |
376 | """
377 | dineshsonachalam@macbook examples % python 6-update-existing-attribute-in-an-item.py
378 | INFO:botocore.credentials:Found credentials in environment variables.
379 | INFO:root:Update is successful
380 | INFO:root:Item: {
381 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
382 | 'role_id': '111',
383 | 'overall_review': {
384 | 'compensation_and_benefits': '3.9/5',
385 | 'overall_rating': '4/5'
386 | },
387 | 'company_name': 'Google',
388 | 'role': 'Staff Software Engineer 2',
389 | 'yearly_hike_percent': Decimal('8'),
390 | 'salary': '$1,50,531',
391 | 'benefits': {
392 | 'Health insurance',
393 | 'Internet, Medical, Edu reimbursements',
394 | 'Travel reimbursements'
395 | }
396 | }
397 | """
398 | ```
399 |
400 |
401 | #### Add a new attribute in an item
402 |
403 |
404 | ```py
405 | from LucidDynamodb import DynamoDb
406 | from LucidDynamodb.exceptions import (
407 | UnexpectedError
408 | )
409 | import logging
410 | logging.basicConfig(level=logging.INFO)
411 |
412 | if __name__ == "__main__":
413 | try:
414 | db = DynamoDb()
415 | db.update_item(
416 | table_name="dev_jobs",
417 | key={
418 | "company_name": "Google",
419 | "role_id": "111"
420 | },
421 | attributes_to_update={
422 | 'overall_review.yearly_bonus_percent': 12
423 | }
424 | )
425 | logging.info("Update is successful")
426 | item = db.read_item(
427 | table_name="dev_jobs",
428 | key={
429 | "company_name": "Google",
430 | "role_id": "111"
431 | }
432 | )
433 | logging.info(f"Item: {item}")
434 | except UnexpectedError as e:
435 | logging.error(f"Update failed - {e}")
436 |
437 | """
438 | dineshsonachalam@macbook examples % python 7-add-a-new-attribute-in-an-item.py
439 | INFO:botocore.credentials:Found credentials in environment variables.
440 | INFO:root:Update is successful
441 | INFO:root:Item: {
442 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
443 | 'role_id': '111',
444 | 'overall_review': {
445 | 'compensation_and_benefits': '3.9/5',
446 | 'overall_rating': '4/5',
447 | 'yearly_bonus_percent': Decimal('12')
448 | },
449 | 'company_name': 'Google',
450 | 'role': 'Staff Software Engineer 2',
451 | 'yearly_hike_percent': Decimal('8'),
452 | 'salary': '$1,50,531',
453 | 'benefits': {
454 | 'Travel reimbursements',
455 | 'Internet, Medical, Edu reimbursements',
456 | 'Health insurance'
457 | }
458 | }
459 | """
460 | ```
461 |
462 |
463 | #### Add an attribute to the list
464 |
465 |
466 | ```py
467 | from LucidDynamodb import DynamoDb
468 | from LucidDynamodb.exceptions import (
469 | UnexpectedError
470 | )
471 | import logging
472 | logging.basicConfig(level=logging.INFO)
473 |
474 | if __name__ == "__main__":
475 | try:
476 | db = DynamoDb()
477 | db.update_item(
478 | table_name="dev_jobs",
479 | key={
480 | "company_name": "Google",
481 | "role_id": "111"
482 | },
483 | attributes_to_update={
484 | 'locations': "Detroit, Michigan"
485 | },
486 | operation="ADD_ATTRIBUTE_TO_LIST"
487 | )
488 | logging.info("Update is successful")
489 | item = db.read_item(
490 | table_name="dev_jobs",
491 | key={
492 | "company_name": "Google",
493 | "role_id": "111"
494 | }
495 | )
496 | logging.info(f"Item: {item}")
497 | except UnexpectedError as e:
498 | logging.error(f"Update failed - {e}")
499 |
500 | """
501 | dineshsonachalam@macbook examples % python 8-add-an-attribute-to-the-list.py
502 | INFO:botocore.credentials:Found credentials in environment variables.
503 | INFO:root:Update is successful
504 | INFO:root:Item: {
505 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
506 | 'role_id': '111',
507 | 'overall_review': {
508 | 'compensation_and_benefits': '3.9/5',
509 | 'overall_rating': '4/5',
510 | 'yearly_bonus_percent': Decimal('12')
511 | },
512 | 'company_name': 'Google',
513 | 'role': 'Staff Software Engineer 2',
514 | 'yearly_hike_percent': Decimal('8'),
515 | 'salary': '$1,50,531',
516 | 'benefits': {
517 | 'Health insurance',
518 | 'Travel reimbursements',
519 | 'Internet, Medical, Edu reimbursements'
520 | }
521 | }
522 | """
523 | ```
524 |
525 |
526 | #### Add an attribute to the string set
527 |
528 |
529 | ```py
530 | from LucidDynamodb import DynamoDb
531 | from LucidDynamodb.exceptions import (
532 | UnexpectedError
533 | )
534 | import logging
535 | logging.basicConfig(level=logging.INFO)
536 |
537 | if __name__ == "__main__":
538 | try:
539 | db = DynamoDb()
540 | db.update_item(
541 | table_name="dev_jobs",
542 | key={
543 | "company_name": "Google",
544 | "role_id": "111"
545 | },
546 | attributes_to_update={
547 | 'benefits': "Free Food"
548 | },
549 | operation="ADD_ATTRIBUTE_TO_STRING_SET"
550 | )
551 | logging.info("Update is successful")
552 | item = db.read_item(
553 | table_name="dev_jobs",
554 | key={
555 | "company_name": "Google",
556 | "role_id": "111"
557 | }
558 | )
559 | logging.info(f"Item: {item}")
560 | except UnexpectedError as e:
561 | logging.error(f"Update failed - {e}")
562 |
563 | """
564 | dineshsonachalam@macbook examples % python 9-add-an-attribute-to-the-string-set.py
565 | INFO:botocore.credentials:Found credentials in environment variables.
566 | INFO:root:Update is successful
567 | INFO:root:Item: {
568 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
569 | 'role_id': '111',
570 | 'overall_review': {
571 | 'compensation_and_benefits': '3.9/5',
572 | 'overall_rating': '4/5',
573 | 'yearly_bonus_percent': Decimal('12')
574 | },
575 | 'company_name': 'Google',
576 | 'role': 'Staff Software Engineer 2',
577 | 'yearly_hike_percent': Decimal('8'),
578 | 'salary': '$1,50,531',
579 | 'benefits': {
580 | 'Travel reimbursements',
581 | 'Free Food',
582 | 'Health insurance',
583 | 'Internet, Medical, Edu reimbursements'
584 | }
585 | }
586 | """
587 | ```
588 |
589 |
590 | #### Increase an existing attribute value
591 |
592 |
593 | ```py
594 | from LucidDynamodb import DynamoDb
595 | from LucidDynamodb.exceptions import (
596 | UnexpectedError
597 | )
598 | import logging
599 | logging.basicConfig(level=logging.INFO)
600 |
601 | if __name__ == "__main__":
602 | try:
603 | db = DynamoDb()
604 | db.increase_attribute_value(
605 | table_name='dev_jobs',
606 | key={
607 | "company_name": "Google",
608 | "role_id": "111"
609 | },
610 | attribute_name="yearly_hike_percent",
611 | increment_value=5
612 | )
613 | logging.info("Attribute value increment completed")
614 | item = db.read_item(
615 | table_name='dev_jobs',
616 | key={
617 | "company_name": "Google",
618 | "role_id": "111"
619 | }
620 | )
621 | logging.info(f"Item: {item}")
622 | except UnexpectedError as e:
623 | logging.error(f"Attribute value increment failed - {e}")
624 |
625 | """
626 | dineshsonachalam@macbook examples % python 10-increase-an-existing-attribute-value.py
627 | INFO:botocore.credentials:Found credentials in environment variables.
628 | INFO:root:Attribute value increment completed
629 | INFO:root:Item: {
630 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
631 | 'role_id': '111',
632 | 'overall_review': {
633 | 'compensation_and_benefits': '3.9/5',
634 | 'overall_rating': '4/5',
635 | 'yearly_bonus_percent': Decimal('12')
636 | },
637 | 'company_name': 'Google',
638 | 'role': 'Staff Software Engineer 2',
639 | 'yearly_hike_percent': Decimal('13'),
640 | 'salary': '$1,50,531',
641 | 'benefits': {
642 | 'Internet, Medical, Edu reimbursements',
643 | 'Free Food',
644 | 'Health insurance',
645 | 'Travel reimbursements'
646 | }
647 | }
648 | """
649 | ```
650 |
651 |
652 | #### Delete an attribute from an item
653 |
654 |
655 | ```py
656 | from LucidDynamodb import DynamoDb
657 | from LucidDynamodb.exceptions import (
658 | UnexpectedError
659 | )
660 | import logging
661 | logging.basicConfig(level=logging.INFO)
662 |
663 | if __name__ == "__main__":
664 | try:
665 | db = DynamoDb()
666 | db.delete_attribute(
667 | table_name="dev_jobs",
668 | key={"company_name": "Google", "role_id": "111"},
669 | attribute_name="yearly_hike_percent")
670 | logging.info("The attribute is deleted successfully")
671 | item = db.read_item(
672 | table_name="dev_jobs",
673 | key={
674 | "company_name": "Google",
675 | "role_id": "111"
676 | }
677 | )
678 | logging.info(f"Item: {item}")
679 | except UnexpectedError as e:
680 | logging.error(f"The attribute delete operation failed - {e}")
681 |
682 | """
683 | dineshsonachalam@macbook examples % python 11-delete-an-attribute-from-an-item.py
684 | INFO:botocore.credentials:Found credentials in environment variables.
685 | INFO:root:The attribute is deleted successfully
686 | INFO:root:Item: {
687 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
688 | 'role_id': '111',
689 | 'overall_review': {
690 | 'compensation_and_benefits': '3.9/5',
691 | 'overall_rating': '4/5',
692 | 'yearly_bonus_percent': Decimal('12')
693 | },
694 | 'company_name': 'Google',
695 | 'role': 'Staff Software Engineer 2',
696 | 'salary': '$1,50,531',
697 | 'benefits': {
698 | 'Travel reimbursements',
699 | 'Free Food',
700 | 'Health insurance',
701 | 'Internet, Medical, Edu reimbursements'
702 | }
703 | }
704 | """
705 | ```
706 |
707 |
708 | #### Delete an attribute from the string set
709 |
710 |
711 | ```py
712 | from LucidDynamodb import DynamoDb
713 | from LucidDynamodb.exceptions import (
714 | UnexpectedError
715 | )
716 | import logging
717 | logging.basicConfig(level=logging.INFO)
718 |
719 | if __name__ == "__main__":
720 | try:
721 | db = DynamoDb()
722 | db.update_item(
723 | table_name="dev_jobs",
724 | key={
725 | "company_name": "Google",
726 | "role_id": "111"
727 | },
728 | attributes_to_update={
729 | 'benefits': "Free Food"
730 | },
731 | operation="DELETE_ATTRIBUTE_FROM_STRING_SET"
732 | )
733 | logging.info("Update is successful")
734 | item = db.read_item(
735 | table_name="dev_jobs",
736 | key={
737 | "company_name": "Google",
738 | "role_id": "111"
739 | }
740 | )
741 | logging.info(f"Item: {item}")
742 | except UnexpectedError as e:
743 | logging.error(f"Update failed - {e}")
744 |
745 | """
746 | dineshsonachalam@macbook examples % python 12-delete-an-attribute-from-the-string-set.py
747 | INFO:botocore.credentials:Found credentials in environment variables.
748 | INFO:root:Update is successful
749 | INFO:root:Item: {
750 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
751 | 'role_id': '111',
752 | 'overall_review': {
753 | 'compensation_and_benefits': '3.9/5',
754 | 'overall_rating': '4/5',
755 | 'yearly_bonus_percent': Decimal('12')
756 | },
757 | 'company_name': 'Google',
758 | 'role': 'Staff Software Engineer 2',
759 | 'salary': '$1,50,531',
760 | 'benefits': {
761 | 'Internet, Medical, Edu reimbursements',
762 | 'Health insurance',
763 | 'Travel reimbursements'
764 | }
765 | }
766 | """
767 | ```
768 |
769 |
770 | #### Delete an item
771 |
772 |
773 | ```py
774 | from LucidDynamodb import DynamoDb
775 | from LucidDynamodb.exceptions import (
776 | UnexpectedError
777 | )
778 | from boto3.dynamodb.conditions import Key
779 | import logging
780 | logging.basicConfig(level=logging.INFO)
781 |
782 | if __name__ == "__main__":
783 | try:
784 | db = DynamoDb()
785 | db.delete_item(
786 | table_name="dev_jobs",
787 | key={
788 | "company_name": "Google",
789 | "role_id": "111"
790 | }
791 | )
792 | logging.info("Item deleted successfully")
793 | items = db.read_items_by_filter(
794 | table_name='dev_jobs',
795 | key_condition_expression=Key("company_name").eq("Google")
796 | )
797 | logging.info(f"Items: {items}")
798 | except UnexpectedError as e:
799 | logging.warning(f"Item delete operation failed - {e}")
800 |
801 | """
802 | dineshsonachalam@macbook examples % python 13-delete-an-item.py
803 | INFO:botocore.credentials:Found credentials in environment variables.
804 | INFO:root:Item deleted successfully
805 | INFO:root:Items: [{
806 | 'locations': ['Mountain View, California'],
807 | 'role_id': '112',
808 | 'overall_review': {
809 | 'compensation_and_benefits': '4.2/5',
810 | 'overall_rating': '3/5'
811 | },
812 | 'company_name': 'Google',
813 | 'role': 'Software Architect',
814 | 'yearly_hike_percent': Decimal('13'),
815 | 'salary': '$4,80,000',
816 | 'benefits': {
817 | 'Internet reimbursements'
818 | }
819 | }]
820 | """
821 | ```
822 |
823 |
824 | #### Delete a table
825 |
826 |
827 | ```py
828 | from LucidDynamodb import DynamoDb
829 | from LucidDynamodb.exceptions import (
830 | TableNotFound
831 | )
832 | import logging
833 | logging.basicConfig(level=logging.INFO)
834 |
835 | if __name__ == "__main__":
836 | try:
837 | db = DynamoDb()
838 | db.delete_table(table_name='dev_jobs')
839 | logging.info("Table deleted successfully")
840 | table_names = db.read_all_table_names()
841 | logging.info(f"Table names: {table_names}")
842 | except TableNotFound as e:
843 | logging.error(f"Table delete operation failed {e}")
844 |
845 | """
846 | dineshsonachalam@macbook examples % python 14-delete-a-table.py
847 | INFO:botocore.credentials:Found credentials in environment variables.
848 | INFO:root:Table deleted successfully
849 | INFO:root:Table names: ['CertMagic', 'dev_test', 'kp-config-v1', 'test-1']
850 | """
851 | ```
852 |
853 |
854 | ## Running Tests
855 |
856 | To run tests, run the following command
857 |
858 | ```bash
859 | pytest -s
860 | ```
861 |
862 | ## License
863 |
864 | [MIT](https://choosealicense.com/licenses/mit/) © [dineshsonachalam](https://www.github.com/dineshsonachalam)
--------------------------------------------------------------------------------
/LucidDynamodb/exceptions.py:
--------------------------------------------------------------------------------
1 | class TableAlreadyExists(Exception):
2 | """Exception is raised when we try to create a table
3 | with a table name that already exists in Dynamodb.
4 |
5 | Attributes:
6 | table_name (str): Table name
7 | message (str): Explanation of the error
8 | """
9 | def __init__(self, table_name, message="Table already exists"):
10 | self.table_name = table_name
11 | self.message = message
12 | super().__init__(self.message)
13 |
14 | def __str__(self):
15 | return f'{self.message} -> {self.table_name}'
16 |
17 | class TableNotFound(Exception):
18 | """Exception is raised when we try to perform a Dynamodb
19 | operation in a table that doesn't exist
20 |
21 | Attributes:
22 | table_name (str): Table name
23 | message (str): Explanation of the error
24 | """
25 | def __init__(self, table_name, message="Table doesn't exist"):
26 | self.table_name = table_name
27 | self.message = message
28 | super().__init__(self.message)
29 |
30 | def __str__(self):
31 | return f'{self.message} -> {self.table_name}'
32 |
33 | class ItemNotFound(Exception):
34 | """Exception is raised when the item is not available
35 |
36 | Attributes:
37 | table_name (str): Table name
38 | key (dict): Partition key, Sort Key(Optional)
39 | message (str): Explanation of the error
40 | """
41 | def __init__(self, table_name, key, message="Item doesn't exist"):
42 | self.table_name = table_name
43 | self.key = key
44 | self.message = message
45 | super().__init__(self.message)
46 |
47 | def __str__(self):
48 | return f'{self.message} -> Key: {self.key}, Table: {self.table_name}'
49 |
50 | class QueryFilterValidationFailed(Exception):
51 | """Exception is raised when the Query filter is not valid
52 |
53 | Attributes:
54 | table_name (str): Table name
55 | message (str): Explanation of the error
56 | """
57 | def __init__(self, table_name, message="Item doesn't exist"):
58 | self.table_name = table_name
59 | self.message = message
60 | super().__init__(self.message)
61 |
62 | def __str__(self):
63 | return f'{self.message} -> Table: {self.table_name}'
64 |
65 | class UnexpectedError(Exception):
66 | """Exception is raised when we perform an unexpected Dynamodb operation.
67 |
68 | Attributes:
69 | message (str): Explanation of the error
70 | """
71 | def __init__(self, message):
72 | self.message = message
73 | super().__init__(self.message)
74 |
75 | def __str__(self):
76 | return f'Unexpected Dynamodb operation -> {self.message}'
--------------------------------------------------------------------------------
/LucidDynamodb/operations.py:
--------------------------------------------------------------------------------
1 | import boto3
2 | from botocore.exceptions import ClientError
3 | from LucidDynamodb.utils import generate_expression_attribute_values
4 | from LucidDynamodb.exceptions import (
5 | TableAlreadyExists,
6 | TableNotFound,
7 | ItemNotFound,
8 | QueryFilterValidationFailed,
9 | UnexpectedError
10 | )
11 |
12 | class DynamoDb:
13 | def __init__(self, region_name=None, aws_access_key_id=None, aws_secret_access_key=None):
14 | self.region_name = region_name
15 | self.aws_access_key_id = aws_access_key_id
16 | self.aws_secret_access_key = aws_secret_access_key
17 | if(self.region_name!=None or self.aws_access_key_id!=None or self.aws_secret_access_key!=None):
18 | self.db = boto3.resource(
19 | "dynamodb",
20 | region_name = region_name,
21 | aws_access_key_id = aws_access_key_id,
22 | aws_secret_access_key = aws_secret_access_key,
23 | )
24 | else:
25 | self.db = boto3.resource("dynamodb")
26 |
27 | def create_table(self, table_name, key_schema, attribute_definitions, provisioned_throughput, global_secondary_indexes=None):
28 | """Create a new table
29 |
30 | Args:
31 | table_name (str): Table name
32 | key_schema (list): A key schema specifies the attributes that make up the Partition key, Sort Key(Optional)) of a table.
33 | attribute_definitions (list): An array of attributes that describe the key schema for the table.
34 | global_secondary_indexes (list, optional): An index with a partition key and a sort key that can be different from those on the base table.
35 | provisioned_throughput (dict): Provisioned throughput settings for this specified table.
36 |
37 | Returns:
38 | bool: Table creation is successful or failed
39 | """
40 | try:
41 | if(len(global_secondary_indexes)>0):
42 | table = self.db.create_table(
43 | TableName=table_name,
44 | KeySchema=key_schema,
45 | AttributeDefinitions=attribute_definitions,
46 | GlobalSecondaryIndexes=global_secondary_indexes,
47 | ProvisionedThroughput=provisioned_throughput
48 | )
49 | else:
50 | table = self.db.create_table(
51 | TableName=table_name,
52 | KeySchema=key_schema,
53 | AttributeDefinitions=attribute_definitions,
54 | ProvisionedThroughput=provisioned_throughput
55 | )
56 | # Wait until the table exists.
57 | table.meta.client.get_waiter('table_exists').wait(TableName=table_name)
58 | return True
59 | except ClientError as e:
60 | if e.response['Error']['Code'] == 'ResourceInUseException':
61 | raise TableAlreadyExists(table_name)
62 | else:
63 | raise UnexpectedError(e)
64 |
65 | def delete_table(self, table_name):
66 | """Delete a table
67 |
68 | Args:
69 | table_name (str): Table name
70 |
71 | Returns:
72 | bool: Table deletion is successful or failed
73 | """
74 | try:
75 | table = self.db.Table(table_name)
76 | table.delete()
77 | table.wait_until_not_exists()
78 | return True
79 | except ClientError as e:
80 | if e.response['Error']['Code'] == 'ResourceNotFoundException':
81 | raise TableNotFound(table_name)
82 | else:
83 | raise UnexpectedError(e)
84 |
85 | def read_all_table_names(self):
86 | """Get all table names
87 |
88 | Returns:
89 | list: List of table names
90 | """
91 | try:
92 | if(self.region_name!=None or self.aws_access_key_id!=None or self.aws_secret_access_key!=None):
93 | db_client = boto3.client(
94 | "dynamodb",
95 | region_name = self.region_name,
96 | aws_access_key_id = self.aws_access_key_id,
97 | aws_secret_access_key = self.aws_secret_access_key,
98 | )
99 | else:
100 | db_client = boto3.client(
101 | "dynamodb"
102 | )
103 | table_names = db_client.list_tables()['TableNames']
104 | return table_names
105 | except ClientError as e:
106 | raise UnexpectedError(e)
107 |
108 | def create_item(self, table_name ,item):
109 | """Create a new Item
110 |
111 | Args:
112 | table_name (str): Table name
113 | item (dict): Item with Partition key, Sort Key(Optional)
114 |
115 | Returns:
116 | bool: Item creation is successful or failed
117 | """
118 | try:
119 | table = self.db.Table(table_name)
120 | table.put_item(Item=item)
121 | return True
122 | except ClientError as e:
123 | raise UnexpectedError(e)
124 |
125 | def delete_item(self, table_name, key, condition_expression = "", expression_attribute_values=None):
126 | """Delete an Item
127 |
128 | Args:
129 | table_name (str): Table name
130 | key (dict): Partition key, Sort Key(Optional)
131 | condition_expression (str, optional): condition_expression to prevent the item from being deleted if the condition is not met.
132 | expression_attribute_values (dict, optional): Expression attribute values.
133 |
134 | Returns:
135 | bool: Item deletion is successful or failed
136 | """
137 | try:
138 | table = self.db.Table(table_name)
139 | if(len(condition_expression)>0 and len(expression_attribute_values)>0):
140 | table.delete_item(
141 | Key=key,
142 | ConditionExpression=condition_expression,
143 | ExpressionAttributeValues=expression_attribute_values
144 | )
145 | else:
146 | table.delete_item(
147 | Key=key
148 | )
149 | return True
150 | except ClientError as e:
151 | raise UnexpectedError(e)
152 |
153 | def read_item(self, table_name, key):
154 | """Read an Item
155 |
156 | Args:
157 | table_name (str): Table name
158 | key (dict): Partition key, Sort Key(Optional)
159 |
160 | Returns:
161 | dict: Item
162 | """
163 | try:
164 | table = self.db.Table(table_name)
165 | response = table.get_item(Key=key)
166 | item = response.get('Item')
167 | if item is not None:
168 | return item
169 | else:
170 | return ItemNotFound(table_name, key)
171 | except ClientError as e:
172 | raise UnexpectedError(e)
173 |
174 | def read_items_by_filter(self, table_name, key_condition_expression, global_secondary_index_name=None):
175 | """Read items by filter
176 | The Query operation will return all of the items from the table or index with that partition key value.
177 |
178 | Args:
179 | table_name (str): Table name
180 | key_condition_expression (boto3.dynamodb.conditions.Equals): Use the KeyConditionExpression parameter to
181 | provide a specific value for the partition key. You can optionally narrow the scope of the Query
182 | operation by specifying a sort key value and a comparison operator in KeyConditionExpression.
183 | global_secondary_index_name (str, optional): Name of the GlobalSecondaryIndex. Defaults to None.
184 |
185 | Returns:
186 | list: Table items
187 | """
188 | try:
189 | table = self.db.Table(table_name)
190 | if global_secondary_index_name != None:
191 | response = table.query(
192 | IndexName=global_secondary_index_name,
193 | KeyConditionExpression=key_condition_expression
194 | )
195 | else:
196 | response = table.query(
197 | KeyConditionExpression=key_condition_expression
198 | )
199 | return response.get('Items')
200 | except ClientError as e:
201 | if e.response['Error']['Code'] == 'ValidationException':
202 | raise QueryFilterValidationFailed(table_name)
203 | else:
204 | raise UnexpectedError(e)
205 |
206 | def update_item(self, table_name, key,
207 | attributes_to_update, operation="UPDATE_EXISTING_ATTRIBUTE_OR_ADD_NEW_ATTRIBUTE"):
208 | """Update an item
209 | Args:
210 | table_name (str): Table name
211 | key (dict): Partition key, Sort Key(Optional)
212 | attributes_to_update (dict): Attributes data with K:V
213 | operation (str, optional): Update operation category
214 | Defaults to UPDATE_EXISTING_ATTRIBUTE_OR_ADD_NEW_ATTRIBUTE.
215 |
216 | Returns:
217 | bool: Attribute update is successful or failed
218 | """
219 | try:
220 | table = self.db.Table(table_name)
221 | update_expression, \
222 | expression_attribute_names, \
223 | expression_attribute_values = generate_expression_attribute_values(attributes_to_update, operation)
224 | if(len(update_expression)>0 and len(expression_attribute_names)>0 \
225 | and len(expression_attribute_values)>0):
226 | table.update_item(
227 | Key=key,
228 | UpdateExpression=update_expression,
229 | ExpressionAttributeNames=expression_attribute_names,
230 | ExpressionAttributeValues=expression_attribute_values,
231 | ReturnValues="ALL_NEW"
232 | )
233 | return True
234 | else:
235 | return False
236 | except ClientError as e:
237 | raise UnexpectedError(e)
238 |
239 | def increase_attribute_value(self, table_name, key, attribute_name, increment_value):
240 | """Increase an existing attribute value
241 |
242 | Args:
243 | table_name (str): Table name
244 | key (dict): Partition key, Sort Key(Optional)
245 | attribute_name (str): Name of the attribute
246 | increment_value (int): Increment value for an attribute
247 |
248 | Returns:
249 | bool: Attribute value is incremented or not
250 | """
251 | try:
252 | table = self.db.Table(table_name)
253 | attributes_to_update = {
254 | attribute_name: increment_value
255 | }
256 | operation = "INCREASE_ATTRIBUTE_VALUE"
257 | update_expression, expression_attribute_names, \
258 | expression_attribute_values = generate_expression_attribute_values(attributes_to_update, operation)
259 | if(len(update_expression)>0 and len(expression_attribute_names)>0 \
260 | and len(expression_attribute_values)>0):
261 | table.update_item(
262 | Key=key,
263 | UpdateExpression=update_expression,
264 | ExpressionAttributeNames=expression_attribute_names,
265 | ExpressionAttributeValues=expression_attribute_values,
266 | ReturnValues="ALL_NEW"
267 | )
268 | return True
269 | else:
270 | return False
271 | except ClientError as e:
272 | raise UnexpectedError(e)
273 |
274 | def delete_attribute(self, table_name, key, attribute_name):
275 | """Delete an attribute from an item
276 |
277 | Args:
278 | table_name (str): Table name
279 | key (dict): Partition key, Sort Key(Optional)
280 | attribute_name (str): Name of the Attribute
281 |
282 | Returns:
283 | bool: Attribute deletion is successful or failed
284 | """
285 | try:
286 | table = self.db.Table(table_name)
287 | table.update_item(
288 | Key=key,
289 | UpdateExpression=f"REMOVE {attribute_name}",
290 | ReturnValues="ALL_NEW"
291 | )
292 | return True
293 | except ClientError as e:
294 | raise UnexpectedError(e)
295 |
--------------------------------------------------------------------------------
/LucidDynamodb/utils.py:
--------------------------------------------------------------------------------
1 | def create_attribute_names(attribute_names):
2 | """Create attribute names
3 | Args:
4 | attribute_names (str): Attribute names
5 | Returns:
6 | str: Expression attribute names
7 | """
8 | expression_attribute_names = {}
9 | for attribute_name in attribute_names:
10 | expression_attribute_names[f"#{attribute_name}"] = attribute_name
11 | return expression_attribute_names
12 |
13 | def generate_expression_attribute_values(attributes_to_update, operation):
14 | """Generate Expression Attribute Values
15 | Args:
16 | attributes_to_update (dict): Attributes to update
17 | operation (str): Operation category
18 | Returns:
19 | update_expression (str): Describes all updates you want to perform on specified item
20 | Example: SET #domain_name = :value1, #owned_by = :value2
21 | expression_attribute_names (dict): Attribute name
22 | Example: {'#domain_name': 'domain_name', '#owned_by': 'owned_by'}
23 | expression_attribute_values (dict): Attribute values
24 | Example: {':value1': 'xbox.com', ':value2': 'Microsoft'}
25 | """
26 | update_expression = ""
27 | expression_attribute_names = {}
28 | expression_attribute_values = {}
29 | counter = 1
30 | for attribute_name, attribute_value in attributes_to_update.items():
31 | expression_attribute_names = create_attribute_names(attribute_name.split('.'))
32 | attribute_name = attribute_name.replace(".", ".#")
33 | if operation == "UPDATE_EXISTING_ATTRIBUTE_OR_ADD_NEW_ATTRIBUTE":
34 | if "SET" not in update_expression:
35 | update_expression = "SET "
36 | update_expression += f"#{attribute_name} = :value{counter}, "
37 | elif operation == "INCREASE_ATTRIBUTE_VALUE":
38 | if "SET" not in update_expression:
39 | update_expression = "SET "
40 | update_expression += f"#{attribute_name} = #{attribute_name} + :value{counter}, "
41 | elif operation == "ADD_ATTRIBUTE_TO_LIST":
42 | if "SET" not in update_expression:
43 | update_expression = "SET "
44 | update_expression += f"#{attribute_name} = list_append(#{attribute_name},:value{counter}), "
45 | elif operation == "ADD_ATTRIBUTE_TO_STRING_SET":
46 | if "ADD" not in update_expression:
47 | update_expression = "ADD "
48 | update_expression += f"#{attribute_name} :value{counter}, "
49 | elif operation == "DELETE_ATTRIBUTE_FROM_STRING_SET":
50 | if "DELETE" not in update_expression:
51 | update_expression = "DELETE "
52 | update_expression += f"#{attribute_name} :value{counter}, "
53 | if operation == "ADD_ATTRIBUTE_TO_LIST":
54 | expression_attribute_values[f":value{counter}"] = [attribute_value]
55 | elif operation == "ADD_ATTRIBUTE_TO_STRING_SET" or operation == "DELETE_ATTRIBUTE_FROM_STRING_SET":
56 | expression_attribute_values[f":value{counter}"] = set([attribute_value])
57 | else:
58 | expression_attribute_values[f":value{counter}"] = attribute_value
59 | counter = counter + 1
60 | update_expression = update_expression.rstrip(", ")
61 | return update_expression, expression_attribute_names, expression_attribute_values
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
6 |
7 | A minimalistic wrapper to AWS DynamoDB
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Useful links:
34 | - See the full documentation at https://lucid-dynamodb.dineshsonachalam.com
35 | - Ask questions in the GitHub issues
36 |
37 | ## Table of contents
38 | - [Installation](#installation)
39 | - [Example](#example)
40 | - [Connect to DynamodDB](#connect-to-dynamodb)
41 | - [Create a new table](#create-a-new-table)
42 | - [Get all table names](#get-all-table-names)
43 | - [Create a New Item](#create-a-new-item)
44 | - [Read an Item](#read-an-item)
45 | - [Read items by filter](#read-items-by-filter)
46 | - [Update existing attribute in an item](#update-existing-attribute-in-an-item)
47 | - [Add a new attribute in an item](#add-a-new-attribute-in-an-item)
48 | - [Add an attribute to the list](#add-an-attribute-to-the-list)
49 | - [Add an attribute to the string set](#add-an-attribute-to-the-string-set)
50 | - [Increase an existing attribute value](#increase-an-existing-attribute-value)
51 | - [Delete an attribute from an item](#delete-an-attribute-from-an-item)
52 | - [Delete an attribute from the string set](#delete-an-attribute-from-the-string-set)
53 | - [Delete an item](#delete-an-item)
54 | - [Delete a table](#delete-a-table)
55 | - [Running tests](#running-tests)
56 | - [Github Workflow Artifacts](#github-workflow-artifacts)
57 | - [License](#license)
58 |
59 | ## Installation
60 |
61 |
62 | ```console
63 | pip install LucidDynamodb
64 | ```
65 |
66 |
67 |
68 | **Note:** Prerequisite for Python3 development
69 |
70 | ## Example
71 |
72 | #### Connect to DynamoDB
73 | You can connect to DynamoDB by following any of these two ways.
74 |
75 | 1. Using AWS config
76 |
77 |
78 | ```py
79 | from LucidDynamodb import DynamoDb
80 | db = DynamoDb()
81 |
82 | """
83 | $ pip install awscli #can add user flag
84 | $ aws configure
85 | AWS Access Key ID [****************ABCD]:[enter your key here]
86 | AWS Secret Access Key [****************xyz]:[enter your secret key here]
87 | Default region name [us-west-2]:[enter your region here]
88 | Default output format [None]:
89 | """
90 | ```
91 |
92 |
93 | 2. Using AWS secret key
94 |
95 |
96 | ```py
97 | from LucidDynamodb import DynamoDb
98 | import os
99 | AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
100 | AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
101 | db = DynamoDb(region_name="us-east-1",
102 | aws_access_key_id=AWS_ACCESS_KEY_ID,
103 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
104 | ```
105 |
106 |
107 | #### Create a new table
108 |
109 |
110 | ```py
111 | from LucidDynamodb import DynamoDb
112 | from LucidDynamodb.exceptions import (
113 | TableAlreadyExists
114 | )
115 | import logging
116 | logging.basicConfig(level=logging.INFO)
117 |
118 | table_schema = {
119 | "TableName": "dev_jobs",
120 | "KeySchema": [{
121 | "AttributeName": "company_name",
122 | "KeyType": "HASH"
123 | },
124 | {
125 | "AttributeName": "role_id",
126 | "KeyType": "RANGE"
127 | }
128 | ],
129 | "AttributeDefinitions": [{
130 | "AttributeName": "company_name",
131 | "AttributeType": "S"
132 | },
133 | {
134 | "AttributeName": "role_id",
135 | "AttributeType": "S"
136 | }
137 | ],
138 | "GlobalSecondaryIndexes": [],
139 | "ProvisionedThroughput": {
140 | "ReadCapacityUnits": 1,
141 | "WriteCapacityUnits": 1
142 | }
143 | }
144 |
145 | if __name__ == "__main__":
146 | try:
147 | db = DynamoDb()
148 | db.create_table(
149 | table_name=table_schema.get("TableName"),
150 | key_schema=table_schema.get("KeySchema"),
151 | attribute_definitions=table_schema.get("AttributeDefinitions"),
152 | global_secondary_indexes=table_schema.get("GlobalSecondaryIndexes"),
153 | provisioned_throughput=table_schema.get("ProvisionedThroughput")
154 | )
155 | logging.info(f"{table_schema.get('TableName')} table created successfully")
156 | except TableAlreadyExists as e:
157 | logging.error(f"{table_schema.get('TableName')} table creation failed - {e}")
158 |
159 | """
160 | dineshsonachalam@macbook examples % python 1-create-a-new-table.py
161 | INFO:botocore.credentials:Found credentials in environment variables.
162 | INFO:root:dev_jobs table created successfully
163 | """
164 | ```
165 |
166 |
167 | #### Get all table names
168 |
169 |
170 | ```py
171 | from LucidDynamodb import DynamoDb
172 | from LucidDynamodb.exceptions import (
173 | UnexpectedError
174 | )
175 | import logging
176 | logging.basicConfig(level=logging.INFO)
177 |
178 | if __name__ == "__main__":
179 | try:
180 | db = DynamoDb()
181 | table_names = db.read_all_table_names()
182 | logging.info(f"Table names: {table_names}")
183 | except UnexpectedError as e:
184 | logging.error(f"Read all table names failed - {e}")
185 |
186 | """
187 | dineshsonachalam@macbook examples % python 2-get-all-table-names.py
188 | INFO:botocore.credentials:Found credentials in environment variables.
189 | INFO:root:Table names: ['CertMagic', 'dev_jobs', 'dev_test', 'kp-config-v1', 'test-1']
190 | """
191 | ```
192 |
193 |
194 | #### Create a new item
195 |
196 |
197 | ```py
198 | from LucidDynamodb import DynamoDb
199 | from LucidDynamodb.exceptions import (
200 | UnexpectedError
201 | )
202 | import logging
203 | logging.basicConfig(level=logging.INFO)
204 |
205 | if __name__ == "__main__":
206 | try:
207 | db = DynamoDb()
208 | db.create_item(
209 | table_name="dev_jobs",
210 | item={
211 | "company_name": "Google",
212 | "role_id": "111",
213 | "role": "Software Engineer 1",
214 | "salary": "$1,50,531",
215 | "locations": ["Mountain View, California", "Austin, Texas", "Chicago, IL"],
216 | "yearly_hike_percent": 8,
217 | "benefits": set(["Internet, Medical, Edu reimbursements",
218 | "Health insurance",
219 | "Travel reimbursements"
220 | ]),
221 | "overall_review":{
222 | "overall_rating" : "4/5",
223 | "compensation_and_benefits": "3.9/5"
224 | }
225 | }
226 | )
227 | logging.info("Item created successfully")
228 | except UnexpectedError as e:
229 | logging.error(f"Item creation failed - {e}")
230 |
231 | """
232 | dineshsonachalam@macbook examples % python 3-create-a-new-item.py
233 | INFO:botocore.credentials:Found credentials in environment variables.
234 | INFO:root:Item created successfully
235 | """
236 | ```
237 |
238 |
239 | #### Read an item
240 |
241 |
242 | ```py
243 | from LucidDynamodb import DynamoDb
244 | from LucidDynamodb.exceptions import (
245 | ItemNotFound
246 | )
247 | import logging
248 | logging.basicConfig(level=logging.INFO)
249 |
250 | if __name__ == "__main__":
251 | try:
252 | db = DynamoDb()
253 | item = db.read_item(
254 | table_name="dev_jobs",
255 | key={
256 | "company_name": "Google",
257 | "role_id": "111"
258 | }
259 | )
260 | logging.info(f"Item: {item}")
261 | except ItemNotFound as e:
262 | logging.error(f"Item doesn't exist - {e}")
263 |
264 | """
265 | dineshsonachalam@macbook examples % python 4-read-an-item.py
266 | INFO:botocore.credentials:Found credentials in environment variables.
267 | INFO:root:Item: {
268 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
269 | 'role_id': '111',
270 | 'overall_review': {
271 | 'compensation_and_benefits': '3.9/5',
272 | 'overall_rating': '4/5'
273 | },
274 | 'company_name': 'Google',
275 | 'role': 'Software Engineer 1',
276 | 'yearly_hike_percent': Decimal('8'),
277 | 'salary': '$1,50,531',
278 | 'benefits': {
279 | 'Travel reimbursements',
280 | 'Internet, Medical, Edu reimbursements',
281 | 'Health insurance'
282 | }
283 | }
284 | """
285 | ```
286 |
287 |
288 | #### Read items by filter
289 |
290 |
291 | ```py
292 | from LucidDynamodb import DynamoDb
293 | from LucidDynamodb.exceptions import (
294 | QueryFilterValidationFailed
295 | )
296 | import logging
297 | from boto3.dynamodb.conditions import Key
298 | logging.basicConfig(level=logging.INFO)
299 |
300 | if __name__ == "__main__":
301 | try:
302 | db = DynamoDb()
303 | db.create_item(
304 | table_name="dev_jobs",
305 | item={
306 | "company_name": "Google",
307 | "role_id": "112",
308 | "role": "Software Architect",
309 | "salary": "$4,80,000",
310 | "locations": ["Mountain View, California"],
311 | "yearly_hike_percent": 13,
312 | "benefits": set(["Internet reimbursements"]),
313 | "overall_review":{
314 | "overall_rating" : "3/5",
315 | "compensation_and_benefits": "4.2/5"
316 | }
317 | }
318 | )
319 | logging.info("Item created successfully")
320 | items = db.read_items_by_filter(
321 | table_name='dev_jobs',
322 | key_condition_expression=Key("company_name").eq("Google")
323 | )
324 | logging.info(f"Items: {items}")
325 | except QueryFilterValidationFailed as e:
326 | logging.error(f"Items doesn't exist - {e}")
327 |
328 | """
329 | dineshsonachalam@macbook examples % python 5-read-items-by-filter.py
330 | INFO:botocore.credentials:Found credentials in environment variables.
331 | INFO:root:Item created successfully
332 | INFO:root:Items: [{
333 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
334 | 'role_id': '111',
335 | 'overall_review': {
336 | 'compensation_and_benefits': '3.9/5',
337 | 'overall_rating': '4/5'
338 | },
339 | 'company_name': 'Google',
340 | 'role': 'Software Engineer 1',
341 | 'yearly_hike_percent': Decimal('8'),
342 | 'salary': '$1,50,531',
343 | 'benefits': {
344 | 'Internet, Medical, Edu reimbursements',
345 | 'Travel reimbursements',
346 | 'Health insurance'
347 | }
348 | }, {
349 | 'locations': ['Mountain View, California'],
350 | 'role_id': '112',
351 | 'overall_review': {
352 | 'compensation_and_benefits': '4.2/5',
353 | 'overall_rating': '3/5'
354 | },
355 | 'company_name': 'Google',
356 | 'role': 'Software Architect',
357 | 'yearly_hike_percent': Decimal('13'),
358 | 'salary': '$4,80,000',
359 | 'benefits': {
360 | 'Internet reimbursements'
361 | }
362 | }]
363 | """
364 | ```
365 |
366 |
367 | #### Update existing attribute in an item
368 |
369 |
370 | ```py
371 | from LucidDynamodb import DynamoDb
372 | from LucidDynamodb.exceptions import (
373 | UnexpectedError
374 | )
375 | import logging
376 | logging.basicConfig(level=logging.INFO)
377 |
378 | if __name__ == "__main__":
379 | try:
380 | db = DynamoDb()
381 | db.update_item(
382 | table_name="dev_jobs",
383 | key={
384 | "company_name": "Google",
385 | "role_id": "111"
386 | },
387 | attributes_to_update={
388 | 'role': 'Staff Software Engineer 2'
389 | }
390 | )
391 | logging.info("Update is successful")
392 | item = db.read_item(
393 | table_name="dev_jobs",
394 | key={
395 | "company_name": "Google",
396 | "role_id": "111"
397 | }
398 | )
399 | logging.info(f"Item: {item}")
400 | except UnexpectedError as e:
401 | logging.error(f"Update failed - {e}")
402 |
403 | """
404 | dineshsonachalam@macbook examples % python 6-update-existing-attribute-in-an-item.py
405 | INFO:botocore.credentials:Found credentials in environment variables.
406 | INFO:root:Update is successful
407 | INFO:root:Item: {
408 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
409 | 'role_id': '111',
410 | 'overall_review': {
411 | 'compensation_and_benefits': '3.9/5',
412 | 'overall_rating': '4/5'
413 | },
414 | 'company_name': 'Google',
415 | 'role': 'Staff Software Engineer 2',
416 | 'yearly_hike_percent': Decimal('8'),
417 | 'salary': '$1,50,531',
418 | 'benefits': {
419 | 'Health insurance',
420 | 'Internet, Medical, Edu reimbursements',
421 | 'Travel reimbursements'
422 | }
423 | }
424 | """
425 | ```
426 |
427 |
428 | #### Add a new attribute in an item
429 |
430 |
431 | ```py
432 | from LucidDynamodb import DynamoDb
433 | from LucidDynamodb.exceptions import (
434 | UnexpectedError
435 | )
436 | import logging
437 | logging.basicConfig(level=logging.INFO)
438 |
439 | if __name__ == "__main__":
440 | try:
441 | db = DynamoDb()
442 | db.update_item(
443 | table_name="dev_jobs",
444 | key={
445 | "company_name": "Google",
446 | "role_id": "111"
447 | },
448 | attributes_to_update={
449 | 'overall_review.yearly_bonus_percent': 12
450 | }
451 | )
452 | logging.info("Update is successful")
453 | item = db.read_item(
454 | table_name="dev_jobs",
455 | key={
456 | "company_name": "Google",
457 | "role_id": "111"
458 | }
459 | )
460 | logging.info(f"Item: {item}")
461 | except UnexpectedError as e:
462 | logging.error(f"Update failed - {e}")
463 |
464 | """
465 | dineshsonachalam@macbook examples % python 7-add-a-new-attribute-in-an-item.py
466 | INFO:botocore.credentials:Found credentials in environment variables.
467 | INFO:root:Update is successful
468 | INFO:root:Item: {
469 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
470 | 'role_id': '111',
471 | 'overall_review': {
472 | 'compensation_and_benefits': '3.9/5',
473 | 'overall_rating': '4/5',
474 | 'yearly_bonus_percent': Decimal('12')
475 | },
476 | 'company_name': 'Google',
477 | 'role': 'Staff Software Engineer 2',
478 | 'yearly_hike_percent': Decimal('8'),
479 | 'salary': '$1,50,531',
480 | 'benefits': {
481 | 'Travel reimbursements',
482 | 'Internet, Medical, Edu reimbursements',
483 | 'Health insurance'
484 | }
485 | }
486 | """
487 | ```
488 |
489 |
490 | #### Add an attribute to the list
491 |
492 |
493 | ```py
494 | from LucidDynamodb import DynamoDb
495 | from LucidDynamodb.exceptions import (
496 | UnexpectedError
497 | )
498 | import logging
499 | logging.basicConfig(level=logging.INFO)
500 |
501 | if __name__ == "__main__":
502 | try:
503 | db = DynamoDb()
504 | db.update_item(
505 | table_name="dev_jobs",
506 | key={
507 | "company_name": "Google",
508 | "role_id": "111"
509 | },
510 | attributes_to_update={
511 | 'locations': "Detroit, Michigan"
512 | },
513 | operation="ADD_ATTRIBUTE_TO_LIST"
514 | )
515 | logging.info("Update is successful")
516 | item = db.read_item(
517 | table_name="dev_jobs",
518 | key={
519 | "company_name": "Google",
520 | "role_id": "111"
521 | }
522 | )
523 | logging.info(f"Item: {item}")
524 | except UnexpectedError as e:
525 | logging.error(f"Update failed - {e}")
526 |
527 | """
528 | dineshsonachalam@macbook examples % python 8-add-an-attribute-to-the-list.py
529 | INFO:botocore.credentials:Found credentials in environment variables.
530 | INFO:root:Update is successful
531 | INFO:root:Item: {
532 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
533 | 'role_id': '111',
534 | 'overall_review': {
535 | 'compensation_and_benefits': '3.9/5',
536 | 'overall_rating': '4/5',
537 | 'yearly_bonus_percent': Decimal('12')
538 | },
539 | 'company_name': 'Google',
540 | 'role': 'Staff Software Engineer 2',
541 | 'yearly_hike_percent': Decimal('8'),
542 | 'salary': '$1,50,531',
543 | 'benefits': {
544 | 'Health insurance',
545 | 'Travel reimbursements',
546 | 'Internet, Medical, Edu reimbursements'
547 | }
548 | }
549 | """
550 | ```
551 |
552 |
553 | #### Add an attribute to the string set
554 |
555 |
556 | ```py
557 | from LucidDynamodb import DynamoDb
558 | from LucidDynamodb.exceptions import (
559 | UnexpectedError
560 | )
561 | import logging
562 | logging.basicConfig(level=logging.INFO)
563 |
564 | if __name__ == "__main__":
565 | try:
566 | db = DynamoDb()
567 | db.update_item(
568 | table_name="dev_jobs",
569 | key={
570 | "company_name": "Google",
571 | "role_id": "111"
572 | },
573 | attributes_to_update={
574 | 'benefits': "Free Food"
575 | },
576 | operation="ADD_ATTRIBUTE_TO_STRING_SET"
577 | )
578 | logging.info("Update is successful")
579 | item = db.read_item(
580 | table_name="dev_jobs",
581 | key={
582 | "company_name": "Google",
583 | "role_id": "111"
584 | }
585 | )
586 | logging.info(f"Item: {item}")
587 | except UnexpectedError as e:
588 | logging.error(f"Update failed - {e}")
589 |
590 | """
591 | dineshsonachalam@macbook examples % python 9-add-an-attribute-to-the-string-set.py
592 | INFO:botocore.credentials:Found credentials in environment variables.
593 | INFO:root:Update is successful
594 | INFO:root:Item: {
595 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
596 | 'role_id': '111',
597 | 'overall_review': {
598 | 'compensation_and_benefits': '3.9/5',
599 | 'overall_rating': '4/5',
600 | 'yearly_bonus_percent': Decimal('12')
601 | },
602 | 'company_name': 'Google',
603 | 'role': 'Staff Software Engineer 2',
604 | 'yearly_hike_percent': Decimal('8'),
605 | 'salary': '$1,50,531',
606 | 'benefits': {
607 | 'Travel reimbursements',
608 | 'Free Food',
609 | 'Health insurance',
610 | 'Internet, Medical, Edu reimbursements'
611 | }
612 | }
613 | """
614 | ```
615 |
616 |
617 | #### Increase an existing attribute value
618 |
619 |
620 | ```py
621 | from LucidDynamodb import DynamoDb
622 | from LucidDynamodb.exceptions import (
623 | UnexpectedError
624 | )
625 | import logging
626 | logging.basicConfig(level=logging.INFO)
627 |
628 | if __name__ == "__main__":
629 | try:
630 | db = DynamoDb()
631 | db.increase_attribute_value(
632 | table_name='dev_jobs',
633 | key={
634 | "company_name": "Google",
635 | "role_id": "111"
636 | },
637 | attribute_name="yearly_hike_percent",
638 | increment_value=5
639 | )
640 | logging.info("Attribute value increment completed")
641 | item = db.read_item(
642 | table_name='dev_jobs',
643 | key={
644 | "company_name": "Google",
645 | "role_id": "111"
646 | }
647 | )
648 | logging.info(f"Item: {item}")
649 | except UnexpectedError as e:
650 | logging.error(f"Attribute value increment failed - {e}")
651 |
652 | """
653 | dineshsonachalam@macbook examples % python 10-increase-an-existing-attribute-value.py
654 | INFO:botocore.credentials:Found credentials in environment variables.
655 | INFO:root:Attribute value increment completed
656 | INFO:root:Item: {
657 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
658 | 'role_id': '111',
659 | 'overall_review': {
660 | 'compensation_and_benefits': '3.9/5',
661 | 'overall_rating': '4/5',
662 | 'yearly_bonus_percent': Decimal('12')
663 | },
664 | 'company_name': 'Google',
665 | 'role': 'Staff Software Engineer 2',
666 | 'yearly_hike_percent': Decimal('13'),
667 | 'salary': '$1,50,531',
668 | 'benefits': {
669 | 'Internet, Medical, Edu reimbursements',
670 | 'Free Food',
671 | 'Health insurance',
672 | 'Travel reimbursements'
673 | }
674 | }
675 | """
676 | ```
677 |
678 |
679 | #### Delete an attribute from an item
680 |
681 |
682 | ```py
683 | from LucidDynamodb import DynamoDb
684 | from LucidDynamodb.exceptions import (
685 | UnexpectedError
686 | )
687 | import logging
688 | logging.basicConfig(level=logging.INFO)
689 |
690 | if __name__ == "__main__":
691 | try:
692 | db = DynamoDb()
693 | db.delete_attribute(
694 | table_name="dev_jobs",
695 | key={"company_name": "Google", "role_id": "111"},
696 | attribute_name="yearly_hike_percent")
697 | logging.info("The attribute is deleted successfully")
698 | item = db.read_item(
699 | table_name="dev_jobs",
700 | key={
701 | "company_name": "Google",
702 | "role_id": "111"
703 | }
704 | )
705 | logging.info(f"Item: {item}")
706 | except UnexpectedError as e:
707 | logging.error(f"The attribute delete operation failed - {e}")
708 |
709 | """
710 | dineshsonachalam@macbook examples % python 11-delete-an-attribute-from-an-item.py
711 | INFO:botocore.credentials:Found credentials in environment variables.
712 | INFO:root:The attribute is deleted successfully
713 | INFO:root:Item: {
714 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
715 | 'role_id': '111',
716 | 'overall_review': {
717 | 'compensation_and_benefits': '3.9/5',
718 | 'overall_rating': '4/5',
719 | 'yearly_bonus_percent': Decimal('12')
720 | },
721 | 'company_name': 'Google',
722 | 'role': 'Staff Software Engineer 2',
723 | 'salary': '$1,50,531',
724 | 'benefits': {
725 | 'Travel reimbursements',
726 | 'Free Food',
727 | 'Health insurance',
728 | 'Internet, Medical, Edu reimbursements'
729 | }
730 | }
731 | """
732 | ```
733 |
734 |
735 | #### Delete an attribute from the string set
736 |
737 |
738 | ```py
739 | from LucidDynamodb import DynamoDb
740 | from LucidDynamodb.exceptions import (
741 | UnexpectedError
742 | )
743 | import logging
744 | logging.basicConfig(level=logging.INFO)
745 |
746 | if __name__ == "__main__":
747 | try:
748 | db = DynamoDb()
749 | db.update_item(
750 | table_name="dev_jobs",
751 | key={
752 | "company_name": "Google",
753 | "role_id": "111"
754 | },
755 | attributes_to_update={
756 | 'benefits': "Free Food"
757 | },
758 | operation="DELETE_ATTRIBUTE_FROM_STRING_SET"
759 | )
760 | logging.info("Update is successful")
761 | item = db.read_item(
762 | table_name="dev_jobs",
763 | key={
764 | "company_name": "Google",
765 | "role_id": "111"
766 | }
767 | )
768 | logging.info(f"Item: {item}")
769 | except UnexpectedError as e:
770 | logging.error(f"Update failed - {e}")
771 |
772 | """
773 | dineshsonachalam@macbook examples % python 12-delete-an-attribute-from-the-string-set.py
774 | INFO:botocore.credentials:Found credentials in environment variables.
775 | INFO:root:Update is successful
776 | INFO:root:Item: {
777 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
778 | 'role_id': '111',
779 | 'overall_review': {
780 | 'compensation_and_benefits': '3.9/5',
781 | 'overall_rating': '4/5',
782 | 'yearly_bonus_percent': Decimal('12')
783 | },
784 | 'company_name': 'Google',
785 | 'role': 'Staff Software Engineer 2',
786 | 'salary': '$1,50,531',
787 | 'benefits': {
788 | 'Internet, Medical, Edu reimbursements',
789 | 'Health insurance',
790 | 'Travel reimbursements'
791 | }
792 | }
793 | """
794 | ```
795 |
796 |
797 | #### Delete an item
798 |
799 |
800 | ```py
801 | from LucidDynamodb import DynamoDb
802 | from LucidDynamodb.exceptions import (
803 | UnexpectedError
804 | )
805 | from boto3.dynamodb.conditions import Key
806 | import logging
807 | logging.basicConfig(level=logging.INFO)
808 |
809 | if __name__ == "__main__":
810 | try:
811 | db = DynamoDb()
812 | db.delete_item(
813 | table_name="dev_jobs",
814 | key={
815 | "company_name": "Google",
816 | "role_id": "111"
817 | }
818 | )
819 | logging.info("Item deleted successfully")
820 | items = db.read_items_by_filter(
821 | table_name='dev_jobs',
822 | key_condition_expression=Key("company_name").eq("Google")
823 | )
824 | logging.info(f"Items: {items}")
825 | except UnexpectedError as e:
826 | logging.warning(f"Item delete operation failed - {e}")
827 |
828 | """
829 | dineshsonachalam@macbook examples % python 13-delete-an-item.py
830 | INFO:botocore.credentials:Found credentials in environment variables.
831 | INFO:root:Item deleted successfully
832 | INFO:root:Items: [{
833 | 'locations': ['Mountain View, California'],
834 | 'role_id': '112',
835 | 'overall_review': {
836 | 'compensation_and_benefits': '4.2/5',
837 | 'overall_rating': '3/5'
838 | },
839 | 'company_name': 'Google',
840 | 'role': 'Software Architect',
841 | 'yearly_hike_percent': Decimal('13'),
842 | 'salary': '$4,80,000',
843 | 'benefits': {
844 | 'Internet reimbursements'
845 | }
846 | }]
847 | """
848 | ```
849 |
850 |
851 | #### Delete a table
852 |
853 |
854 | ```py
855 | from LucidDynamodb import DynamoDb
856 | from LucidDynamodb.exceptions import (
857 | TableNotFound
858 | )
859 | import logging
860 | logging.basicConfig(level=logging.INFO)
861 |
862 | if __name__ == "__main__":
863 | try:
864 | db = DynamoDb()
865 | db.delete_table(table_name='dev_jobs')
866 | logging.info("Table deleted successfully")
867 | table_names = db.read_all_table_names()
868 | logging.info(f"Table names: {table_names}")
869 | except TableNotFound as e:
870 | logging.error(f"Table delete operation failed {e}")
871 |
872 | """
873 | dineshsonachalam@macbook examples % python 14-delete-a-table.py
874 | INFO:botocore.credentials:Found credentials in environment variables.
875 | INFO:root:Table deleted successfully
876 | INFO:root:Table names: ['CertMagic', 'dev_test', 'kp-config-v1', 'test-1']
877 | """
878 | ```
879 |
880 |
881 | ## Running Tests
882 |
883 | To run tests, run the following command
884 |
885 | ```bash
886 | pytest -s
887 | ```
888 |
889 | ## License
890 |
891 | [MIT](https://choosealicense.com/licenses/mit/) © [dineshsonachalam](https://www.github.com/dineshsonachalam)
892 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cleanup() {
3 | rm -rf build dist *.egg-info
4 | }
5 |
6 | Main() {
7 | cleanup
8 | # Compliling the package
9 | python3 setup.py sdist bdist_wheel
10 | # Upload projects to pypi
11 | twine upload --username $PYPI_USERNAME --password $PYPI_PASSWORD --skip-existing dist/*
12 | cleanup
13 | }
14 |
15 | Main
16 |
--------------------------------------------------------------------------------
/examples/1-create-a-new-table.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | TableAlreadyExists
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | table_schema = {
9 | "TableName": "dev_jobs",
10 | "KeySchema": [{
11 | "AttributeName": "company_name",
12 | "KeyType": "HASH"
13 | },
14 | {
15 | "AttributeName": "role_id",
16 | "KeyType": "RANGE"
17 | }
18 | ],
19 | "AttributeDefinitions": [{
20 | "AttributeName": "company_name",
21 | "AttributeType": "S"
22 | },
23 | {
24 | "AttributeName": "role_id",
25 | "AttributeType": "S"
26 | }
27 | ],
28 | "GlobalSecondaryIndexes": [],
29 | "ProvisionedThroughput": {
30 | "ReadCapacityUnits": 1,
31 | "WriteCapacityUnits": 1
32 | }
33 | }
34 |
35 | if __name__ == "__main__":
36 | try:
37 | db = DynamoDb()
38 | db.create_table(
39 | table_name=table_schema.get("TableName"),
40 | key_schema=table_schema.get("KeySchema"),
41 | attribute_definitions=table_schema.get("AttributeDefinitions"),
42 | global_secondary_indexes=table_schema.get("GlobalSecondaryIndexes"),
43 | provisioned_throughput=table_schema.get("ProvisionedThroughput")
44 | )
45 | logging.info(f"{table_schema.get('TableName')} table created successfully")
46 | except TableAlreadyExists as e:
47 | logging.error(f"{table_schema.get('TableName')} table creation failed - {e}")
48 |
49 | """
50 | dineshsonachalam@macbook examples % python 1-create-a-new-table.py
51 | INFO:botocore.credentials:Found credentials in environment variables.
52 | INFO:root:dev_jobs table created successfully
53 | """
--------------------------------------------------------------------------------
/examples/10-increase-an-existing-attribute-value.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.increase_attribute_value(
12 | table_name='dev_jobs',
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | },
17 | attribute_name="yearly_hike_percent",
18 | increment_value=5
19 | )
20 | logging.info("Attribute value increment completed")
21 | item = db.read_item(
22 | table_name='dev_jobs',
23 | key={
24 | "company_name": "Google",
25 | "role_id": "111"
26 | }
27 | )
28 | logging.info(f"Item: {item}")
29 | except UnexpectedError as e:
30 | logging.error(f"Attribute value increment failed - {e}")
31 |
32 | """
33 | dineshsonachalam@macbook examples % python 10-increase-an-existing-attribute-value.py
34 | INFO:botocore.credentials:Found credentials in environment variables.
35 | INFO:root:Attribute value increment completed
36 | INFO:root:Item: {
37 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
38 | 'role_id': '111',
39 | 'overall_review': {
40 | 'compensation_and_benefits': '3.9/5',
41 | 'overall_rating': '4/5',
42 | 'yearly_bonus_percent': Decimal('12')
43 | },
44 | 'company_name': 'Google',
45 | 'role': 'Staff Software Engineer 2',
46 | 'yearly_hike_percent': Decimal('13'),
47 | 'salary': '$1,50,531',
48 | 'benefits': {
49 | 'Internet, Medical, Edu reimbursements',
50 | 'Free Food',
51 | 'Health insurance',
52 | 'Travel reimbursements'
53 | }
54 | }
55 | """
--------------------------------------------------------------------------------
/examples/11-delete-an-attribute-from-an-item.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.delete_attribute(
12 | table_name="dev_jobs",
13 | key={"company_name": "Google", "role_id": "111"},
14 | attribute_name="yearly_hike_percent")
15 | logging.info("The attribute is deleted successfully")
16 | item = db.read_item(
17 | table_name="dev_jobs",
18 | key={
19 | "company_name": "Google",
20 | "role_id": "111"
21 | }
22 | )
23 | logging.info(f"Item: {item}")
24 | except UnexpectedError as e:
25 | logging.error(f"The attribute delete operation failed - {e}")
26 |
27 | """
28 | dineshsonachalam@macbook examples % python 11-delete-an-attribute-from-an-item.py
29 | INFO:botocore.credentials:Found credentials in environment variables.
30 | INFO:root:The attribute is deleted successfully
31 | INFO:root:Item: {
32 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
33 | 'role_id': '111',
34 | 'overall_review': {
35 | 'compensation_and_benefits': '3.9/5',
36 | 'overall_rating': '4/5',
37 | 'yearly_bonus_percent': Decimal('12')
38 | },
39 | 'company_name': 'Google',
40 | 'role': 'Staff Software Engineer 2',
41 | 'salary': '$1,50,531',
42 | 'benefits': {
43 | 'Travel reimbursements',
44 | 'Free Food',
45 | 'Health insurance',
46 | 'Internet, Medical, Edu reimbursements'
47 | }
48 | }
49 | """
--------------------------------------------------------------------------------
/examples/12-delete-an-attribute-from-the-string-set.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.update_item(
12 | table_name="dev_jobs",
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | },
17 | attributes_to_update={
18 | 'benefits': "Free Food"
19 | },
20 | operation="DELETE_ATTRIBUTE_FROM_STRING_SET"
21 | )
22 | logging.info("Update is successful")
23 | item = db.read_item(
24 | table_name="dev_jobs",
25 | key={
26 | "company_name": "Google",
27 | "role_id": "111"
28 | }
29 | )
30 | logging.info(f"Item: {item}")
31 | except UnexpectedError as e:
32 | logging.error(f"Update failed - {e}")
33 |
34 | """
35 | dineshsonachalam@macbook examples % python 12-delete-an-attribute-from-the-string-set.py
36 | INFO:botocore.credentials:Found credentials in environment variables.
37 | INFO:root:Update is successful
38 | INFO:root:Item: {
39 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
40 | 'role_id': '111',
41 | 'overall_review': {
42 | 'compensation_and_benefits': '3.9/5',
43 | 'overall_rating': '4/5',
44 | 'yearly_bonus_percent': Decimal('12')
45 | },
46 | 'company_name': 'Google',
47 | 'role': 'Staff Software Engineer 2',
48 | 'salary': '$1,50,531',
49 | 'benefits': {
50 | 'Internet, Medical, Edu reimbursements',
51 | 'Health insurance',
52 | 'Travel reimbursements'
53 | }
54 | }
55 | """
--------------------------------------------------------------------------------
/examples/13-delete-an-item.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | from boto3.dynamodb.conditions import Key
6 | import logging
7 | logging.basicConfig(level=logging.INFO)
8 |
9 | if __name__ == "__main__":
10 | try:
11 | db = DynamoDb()
12 | db.delete_item(
13 | table_name="dev_jobs",
14 | key={
15 | "company_name": "Google",
16 | "role_id": "111"
17 | }
18 | )
19 | logging.info("Item deleted successfully")
20 | items = db.read_items_by_filter(
21 | table_name='dev_jobs',
22 | key_condition_expression=Key("company_name").eq("Google")
23 | )
24 | logging.info(f"Items: {items}")
25 | except UnexpectedError as e:
26 | logging.warning(f"Item delete operation failed - {e}")
27 |
28 | """
29 | dineshsonachalam@macbook examples % python 13-delete-an-item.py
30 | INFO:botocore.credentials:Found credentials in environment variables.
31 | INFO:root:Item deleted successfully
32 | INFO:root:Items: [{
33 | 'locations': ['Mountain View, California'],
34 | 'role_id': '112',
35 | 'overall_review': {
36 | 'compensation_and_benefits': '4.2/5',
37 | 'overall_rating': '3/5'
38 | },
39 | 'company_name': 'Google',
40 | 'role': 'Software Architect',
41 | 'yearly_hike_percent': Decimal('13'),
42 | 'salary': '$4,80,000',
43 | 'benefits': {
44 | 'Internet reimbursements'
45 | }
46 | }]
47 | """
--------------------------------------------------------------------------------
/examples/14-delete-a-table.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | TableNotFound
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.delete_table(table_name='dev_jobs')
12 | logging.info("Table deleted successfully")
13 | table_names = db.read_all_table_names()
14 | logging.info(f"Table names: {table_names}")
15 | except TableNotFound as e:
16 | logging.error(f"Table delete operation failed {e}")
17 |
18 | """
19 | dineshsonachalam@macbook examples % python 14-delete-a-table.py
20 | INFO:botocore.credentials:Found credentials in environment variables.
21 | INFO:root:Table deleted successfully
22 | INFO:root:Table names: ['CertMagic', 'dev_test', 'kp-config-v1', 'test-1']
23 | """
--------------------------------------------------------------------------------
/examples/2-get-all-table-names.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | table_names = db.read_all_table_names()
12 | logging.info(f"Table names: {table_names}")
13 | except UnexpectedError as e:
14 | logging.error(f"Read all table names failed - {e}")
15 |
16 | """
17 | dineshsonachalam@macbook examples % python 2-get-all-table-names.py
18 | INFO:botocore.credentials:Found credentials in environment variables.
19 | INFO:root:Table names: ['CertMagic', 'dev_jobs', 'dev_test', 'kp-config-v1', 'test-1']
20 | """
--------------------------------------------------------------------------------
/examples/3-create-a-new-item.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.create_item(
12 | table_name="dev_jobs",
13 | item={
14 | "company_name": "Google",
15 | "role_id": "111",
16 | "role": "Software Engineer 1",
17 | "salary": "$1,50,531",
18 | "locations": ["Mountain View, California", "Austin, Texas", "Chicago, IL"],
19 | "yearly_hike_percent": 8,
20 | "benefits": set(["Internet, Medical, Edu reimbursements",
21 | "Health insurance",
22 | "Travel reimbursements"
23 | ]),
24 | "overall_review":{
25 | "overall_rating" : "4/5",
26 | "compensation_and_benefits": "3.9/5"
27 | }
28 | }
29 | )
30 | logging.info("Item created successfully")
31 | except UnexpectedError as e:
32 | logging.error(f"Item creation failed - {e}")
33 |
34 | """
35 | dineshsonachalam@macbook examples % python 3-create-a-new-item.py
36 | INFO:botocore.credentials:Found credentials in environment variables.
37 | INFO:root:Item created successfully
38 | """
--------------------------------------------------------------------------------
/examples/4-read-an-item.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | ItemNotFound
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | item = db.read_item(
12 | table_name="dev_jobs",
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | }
17 | )
18 | logging.info(f"Item: {item}")
19 | except ItemNotFound as e:
20 | logging.error(f"Item doesn't exist - {e}")
21 |
22 | """
23 | dineshsonachalam@macbook examples % python 4-read-an-item.py
24 | INFO:botocore.credentials:Found credentials in environment variables.
25 | INFO:root:Item: {
26 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
27 | 'role_id': '111',
28 | 'overall_review': {
29 | 'compensation_and_benefits': '3.9/5',
30 | 'overall_rating': '4/5'
31 | },
32 | 'company_name': 'Google',
33 | 'role': 'Software Engineer 1',
34 | 'yearly_hike_percent': Decimal('8'),
35 | 'salary': '$1,50,531',
36 | 'benefits': {
37 | 'Travel reimbursements',
38 | 'Internet, Medical, Edu reimbursements',
39 | 'Health insurance'
40 | }
41 | }
42 | """
--------------------------------------------------------------------------------
/examples/5-read-items-by-filter.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | QueryFilterValidationFailed
4 | )
5 | import logging
6 | from boto3.dynamodb.conditions import Key
7 | logging.basicConfig(level=logging.INFO)
8 |
9 | if __name__ == "__main__":
10 | try:
11 | db = DynamoDb()
12 | db.create_item(
13 | table_name="dev_jobs",
14 | item={
15 | "company_name": "Google",
16 | "role_id": "112",
17 | "role": "Software Architect",
18 | "salary": "$4,80,000",
19 | "locations": ["Mountain View, California"],
20 | "yearly_hike_percent": 13,
21 | "benefits": set(["Internet reimbursements"]),
22 | "overall_review":{
23 | "overall_rating" : "3/5",
24 | "compensation_and_benefits": "4.2/5"
25 | }
26 | }
27 | )
28 | logging.info("Item created successfully")
29 | items = db.read_items_by_filter(
30 | table_name='dev_jobs',
31 | key_condition_expression=Key("company_name").eq("Google")
32 | )
33 | logging.info(f"Items: {items}")
34 | except QueryFilterValidationFailed as e:
35 | logging.error(f"Items doesn't exist - {e}")
36 |
37 | """
38 | dineshsonachalam@macbook examples % python 5-read-items-by-filter.py
39 | INFO:botocore.credentials:Found credentials in environment variables.
40 | INFO:root:Item created successfully
41 | INFO:root:Items: [{
42 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
43 | 'role_id': '111',
44 | 'overall_review': {
45 | 'compensation_and_benefits': '3.9/5',
46 | 'overall_rating': '4/5'
47 | },
48 | 'company_name': 'Google',
49 | 'role': 'Software Engineer 1',
50 | 'yearly_hike_percent': Decimal('8'),
51 | 'salary': '$1,50,531',
52 | 'benefits': {
53 | 'Internet, Medical, Edu reimbursements',
54 | 'Travel reimbursements',
55 | 'Health insurance'
56 | }
57 | }, {
58 | 'locations': ['Mountain View, California'],
59 | 'role_id': '112',
60 | 'overall_review': {
61 | 'compensation_and_benefits': '4.2/5',
62 | 'overall_rating': '3/5'
63 | },
64 | 'company_name': 'Google',
65 | 'role': 'Software Architect',
66 | 'yearly_hike_percent': Decimal('13'),
67 | 'salary': '$4,80,000',
68 | 'benefits': {
69 | 'Internet reimbursements'
70 | }
71 | }]
72 | """
--------------------------------------------------------------------------------
/examples/6-update-existing-attribute-in-an-item.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.update_item(
12 | table_name="dev_jobs",
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | },
17 | attributes_to_update={
18 | 'role': 'Staff Software Engineer 2'
19 | }
20 | )
21 | logging.info("Update is successful")
22 | item = db.read_item(
23 | table_name="dev_jobs",
24 | key={
25 | "company_name": "Google",
26 | "role_id": "111"
27 | }
28 | )
29 | logging.info(f"Item: {item}")
30 | except UnexpectedError as e:
31 | logging.error(f"Update failed - {e}")
32 |
33 | """
34 | dineshsonachalam@macbook examples % python 6-update-existing-attribute-in-an-item.py
35 | INFO:botocore.credentials:Found credentials in environment variables.
36 | INFO:root:Update is successful
37 | INFO:root:Item: {
38 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
39 | 'role_id': '111',
40 | 'overall_review': {
41 | 'compensation_and_benefits': '3.9/5',
42 | 'overall_rating': '4/5'
43 | },
44 | 'company_name': 'Google',
45 | 'role': 'Staff Software Engineer 2',
46 | 'yearly_hike_percent': Decimal('8'),
47 | 'salary': '$1,50,531',
48 | 'benefits': {
49 | 'Health insurance',
50 | 'Internet, Medical, Edu reimbursements',
51 | 'Travel reimbursements'
52 | }
53 | }
54 | """
--------------------------------------------------------------------------------
/examples/7-add-a-new-attribute-in-an-item.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.update_item(
12 | table_name="dev_jobs",
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | },
17 | attributes_to_update={
18 | 'overall_review.yearly_bonus_percent': 12
19 | }
20 | )
21 | logging.info("Update is successful")
22 | item = db.read_item(
23 | table_name="dev_jobs",
24 | key={
25 | "company_name": "Google",
26 | "role_id": "111"
27 | }
28 | )
29 | logging.info(f"Item: {item}")
30 | except UnexpectedError as e:
31 | logging.error(f"Update failed - {e}")
32 |
33 | """
34 | dineshsonachalam@macbook examples % python 7-add-a-new-attribute-in-an-item.py
35 | INFO:botocore.credentials:Found credentials in environment variables.
36 | INFO:root:Update is successful
37 | INFO:root:Item: {
38 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL'],
39 | 'role_id': '111',
40 | 'overall_review': {
41 | 'compensation_and_benefits': '3.9/5',
42 | 'overall_rating': '4/5',
43 | 'yearly_bonus_percent': Decimal('12')
44 | },
45 | 'company_name': 'Google',
46 | 'role': 'Staff Software Engineer 2',
47 | 'yearly_hike_percent': Decimal('8'),
48 | 'salary': '$1,50,531',
49 | 'benefits': {
50 | 'Travel reimbursements',
51 | 'Internet, Medical, Edu reimbursements',
52 | 'Health insurance'
53 | }
54 | }
55 | """
--------------------------------------------------------------------------------
/examples/8-add-an-attribute-to-the-list.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.update_item(
12 | table_name="dev_jobs",
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | },
17 | attributes_to_update={
18 | 'locations': "Detroit, Michigan"
19 | },
20 | operation="ADD_ATTRIBUTE_TO_LIST"
21 | )
22 | logging.info("Update is successful")
23 | item = db.read_item(
24 | table_name="dev_jobs",
25 | key={
26 | "company_name": "Google",
27 | "role_id": "111"
28 | }
29 | )
30 | logging.info(f"Item: {item}")
31 | except UnexpectedError as e:
32 | logging.error(f"Update failed - {e}")
33 |
34 | """
35 | dineshsonachalam@macbook examples % python 8-add-an-attribute-to-the-list.py
36 | INFO:botocore.credentials:Found credentials in environment variables.
37 | INFO:root:Update is successful
38 | INFO:root:Item: {
39 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
40 | 'role_id': '111',
41 | 'overall_review': {
42 | 'compensation_and_benefits': '3.9/5',
43 | 'overall_rating': '4/5',
44 | 'yearly_bonus_percent': Decimal('12')
45 | },
46 | 'company_name': 'Google',
47 | 'role': 'Staff Software Engineer 2',
48 | 'yearly_hike_percent': Decimal('8'),
49 | 'salary': '$1,50,531',
50 | 'benefits': {
51 | 'Health insurance',
52 | 'Travel reimbursements',
53 | 'Internet, Medical, Edu reimbursements'
54 | }
55 | }
56 | """
--------------------------------------------------------------------------------
/examples/9-add-an-attribute-to-the-string-set.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | UnexpectedError
4 | )
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | if __name__ == "__main__":
9 | try:
10 | db = DynamoDb()
11 | db.update_item(
12 | table_name="dev_jobs",
13 | key={
14 | "company_name": "Google",
15 | "role_id": "111"
16 | },
17 | attributes_to_update={
18 | 'benefits': "Free Food"
19 | },
20 | operation="ADD_ATTRIBUTE_TO_STRING_SET"
21 | )
22 | logging.info("Update is successful")
23 | item = db.read_item(
24 | table_name="dev_jobs",
25 | key={
26 | "company_name": "Google",
27 | "role_id": "111"
28 | }
29 | )
30 | logging.info(f"Item: {item}")
31 | except UnexpectedError as e:
32 | logging.error(f"Update failed - {e}")
33 |
34 | """
35 | dineshsonachalam@macbook examples % python 9-add-an-attribute-to-the-string-set.py
36 | INFO:botocore.credentials:Found credentials in environment variables.
37 | INFO:root:Update is successful
38 | INFO:root:Item: {
39 | 'locations': ['Mountain View, California', 'Austin, Texas', 'Chicago, IL', 'Detroit, Michigan'],
40 | 'role_id': '111',
41 | 'overall_review': {
42 | 'compensation_and_benefits': '3.9/5',
43 | 'overall_rating': '4/5',
44 | 'yearly_bonus_percent': Decimal('12')
45 | },
46 | 'company_name': 'Google',
47 | 'role': 'Staff Software Engineer 2',
48 | 'yearly_hike_percent': Decimal('8'),
49 | 'salary': '$1,50,531',
50 | 'benefits': {
51 | 'Travel reimbursements',
52 | 'Free Food',
53 | 'Health insurance',
54 | 'Internet, Medical, Edu reimbursements'
55 | }
56 | }
57 | """
--------------------------------------------------------------------------------
/examples/using-aws-config-to-connect-to-dynamodb.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | db = DynamoDb()
3 |
4 | """
5 | $ pip install awscli #can add user flag
6 | $ aws configure
7 | AWS Access Key ID [****************ABCD]:[enter your key here]
8 | AWS Secret Access Key [****************xyz]:[enter your secret key here]
9 | Default region name [us-west-2]:[enter your region here]
10 | Default output format [None]:
11 | """
--------------------------------------------------------------------------------
/examples/using-aws-secret-to-connect-to-dynamodb.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | import os
3 | AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
4 | AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
5 | db = DynamoDb(region_name="us-east-1",
6 | aws_access_key_id=AWS_ACCESS_KEY_ID,
7 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
--------------------------------------------------------------------------------
/helm/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v2
2 | appVersion: "1.0"
3 | description: Helm chart for tech-course-search engine app
4 | name: tech-courses-search-engine
5 | owner: dineshsonachalam
6 | version: 0.1.0
7 |
--------------------------------------------------------------------------------
/helm/templates/lucid-docs/deployment.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | labels:
5 | app: {{ .Values.lucidDocs.appName }}
6 | name: {{ .Values.lucidDocs.appName }}
7 | namespace: {{ .Values.namespace }}
8 | spec:
9 | replicas: {{ .Values.replicas }}
10 | selector:
11 | matchLabels:
12 | app: {{ .Values.lucidDocs.appName }}
13 | template:
14 | metadata:
15 | labels:
16 | app: {{ .Values.lucidDocs.appName }}
17 | spec:
18 | containers:
19 | - name: {{ .Values.lucidDocs.appName }}
20 | image: {{ .Values.lucidDocs.image }}
21 | imagePullPolicy: Always
22 | ports:
23 | - containerPort: {{ .Values.lucidDocs.containerPort }}
24 | name: {{ .Values.lucidDocs.appName }}
25 |
--------------------------------------------------------------------------------
/helm/templates/lucid-docs/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: traefik.containo.us/v1alpha1
2 | kind: IngressRoute
3 | metadata:
4 | name: {{ .Values.lucidDocs.appName }}
5 | namespace: {{ .Values.namespace }}
6 | spec:
7 | entryPoints:
8 | - web
9 | routes:
10 | - match: {{ .Values.lucidDocs.ingressRoute }}
11 | kind: Rule
12 | services:
13 | - name: {{ .Values.lucidDocs.appName }}
14 | port: {{ .Values.lucidDocs.containerPort }}
--------------------------------------------------------------------------------
/helm/templates/lucid-docs/service.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: {{ .Values.lucidDocs.appName }}
5 | namespace: {{ .Values.namespace }}
6 | spec:
7 | ports:
8 | - protocol: TCP
9 | name: web
10 | port: {{ .Values.lucidDocs.containerPort }}
11 | selector:
12 | app: {{ .Values.lucidDocs.appName }}
--------------------------------------------------------------------------------
/helm/values.yaml:
--------------------------------------------------------------------------------
1 | namespace: dinesh
2 | replicas: 1
3 |
4 | lucidDocs:
5 | image: dineshsonachalam/lucid-dynamodb-docs:latest
6 | containerPort: 3000
7 | appName: lucid-docs
8 | ingressRoute: (Host(`lucid-dynamodb.dineshsonachalam.com`))
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lucid-dynamodb",
3 | "version": "1.0.0",
4 | "description": "
A minimalistic wrapper to AWS DynamoDB
",
5 | "main": "server.js",
6 | "directories": {
7 | "doc": "docs",
8 | "example": "examples",
9 | "test": "tests"
10 | },
11 | "scripts": {
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "start": "node server.js"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/dineshsonachalam/lucid-dynamodb.git"
18 | },
19 | "keywords": [],
20 | "author": "",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/dineshsonachalam/lucid-dynamodb/issues"
24 | },
25 | "homepage": "https://github.com/dineshsonachalam/lucid-dynamodb#readme",
26 | "dependencies": {
27 | "express": "^4.17.1"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/requirements-dev.txt:
--------------------------------------------------------------------------------
1 | boto3>=1.17.78
2 | botocore>=1.20.78
3 | semantic-version==2.8.5
4 | twine==3.4.1
5 | pytest==6.2.4
6 | pytest-cov==2.12.1
7 | requests==2.25.1
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | boto3>=1.17.78
2 | botocore>=1.20.78
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const path = require("path");
3 | const app = express();
4 |
5 | app.use(express.static(path.join(__dirname, "LucidDynamodb")));
6 |
7 | app.get("/", function (req, res) {
8 | res.sendFile(path.join(__dirname, "LucidDynamodb", "index.html"));
9 | });
10 |
11 | app.get("/*", function(req, res) {
12 | res.sendFile(path.join(__dirname, "LucidDynamodb", "index.html"), function(err) {
13 | if (err) {
14 | res.status(500).send(err);
15 | }
16 | });
17 | });
18 |
19 | app.listen(3000);
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup, find_packages
2 | import requests
3 | import semantic_version
4 |
5 | install_requires = [
6 | 'boto3>=1.17.78',
7 | 'botocore>=1.20.78'
8 | ]
9 |
10 | def get_lucid_dynamodb_version():
11 | url = "https://pypi.org/pypi/LucidDynamodb/json"
12 | response = requests.request("GET", url, headers={}, data={})
13 | result = response.json()
14 | lucid_dynamodb_version = str(result.get("info").get("version"))
15 | current_version = semantic_version.Version(lucid_dynamodb_version)
16 | next_version = current_version.next_patch()
17 | return next_version
18 |
19 | setup(
20 | name="LucidDynamodb",
21 | version=str(get_lucid_dynamodb_version()),
22 | author="Dinesh Sonachalam",
23 | author_email="dineshsonachalam@gmail.com",
24 | description="A simple Python wrapper to AWS Dynamodb",
25 | url="https://github.com/dineshsonachalam/Lucid-Dynamodb",
26 | long_description=open('README.md').read(),
27 | long_description_content_type='text/markdown',
28 | zip_safe=False,
29 | license='MIT',
30 | keywords='python dynamodb amazon',
31 | python_requires=">=3.1",
32 | install_requires=install_requires,
33 | packages=find_packages(),
34 | classifiers=[
35 | "Programming Language :: Python :: 3",
36 | "License :: OSI Approved :: MIT License",
37 | "Operating System :: OS Independent",
38 | ]
39 | )
--------------------------------------------------------------------------------
/sonar-project.properties:
--------------------------------------------------------------------------------
1 | sonar.projectKey=lucid-dynamodb
2 | sonar.organization=dineshsonachalam
3 |
4 | # This is the name and version displayed in the SonarCloud UI.
5 | sonar.projectName=lucid-dynamodb
6 | sonar.projectVersion=1.0
7 | sonar.exclusions=**/examples/*.py,**/tests/*.py
8 | sonar.coverage.exclusions=setup.py,server.js
9 | sonar.python.coverage.reportPaths=coverage.xml
10 |
11 | # # Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
12 | # sonar.sources=.
13 |
14 | # sonar.python.coverage.reportPaths=/home/runner/work/lucid-dynamodb/lucid-dynamodb/coverage.xml
15 |
16 | # # Encoding of the source code. Default is default system encoding
17 | # sonar.sourceEncoding=UTF-8
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/test_crud.py:
--------------------------------------------------------------------------------
1 | from LucidDynamodb import DynamoDb
2 | from LucidDynamodb.exceptions import (
3 | TableAlreadyExists,
4 | TableNotFound,
5 | ItemNotFound,
6 | QueryFilterValidationFailed,
7 | UnexpectedError
8 | )
9 | import os
10 | import uuid
11 | import pytest
12 | from boto3.dynamodb.conditions import Key
13 |
14 | AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY_ID")
15 | AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_ACCESS_KEY")
16 |
17 | ITEM1_PARTITION_KEY = str(uuid.uuid4())
18 |
19 | table_schema = {
20 | "TableName": "dev_jobs_test",
21 | "KeySchema": [
22 | {
23 | "AttributeName": "company_name",
24 | "KeyType": "HASH"
25 | },
26 | {
27 | "AttributeName": "role_id",
28 | "KeyType": "RANGE"
29 | }
30 | ],
31 | "AttributeDefinitions": [
32 | {
33 | "AttributeName": "company_name",
34 | "AttributeType": "S"
35 | },
36 | {
37 | "AttributeName": "role_id",
38 | "AttributeType": "S"
39 | }
40 | ],
41 | "GlobalSecondaryIndexes": [],
42 | "ProvisionedThroughput": {
43 | "ReadCapacityUnits": 1,
44 | "WriteCapacityUnits": 1
45 | }
46 | }
47 | db = DynamoDb(region_name="us-east-1",
48 | aws_access_key_id=AWS_ACCESS_KEY_ID,
49 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
50 |
51 | db_wrong_credentials = DynamoDb(region_name="us-east-1",
52 | aws_access_key_id=AWS_ACCESS_KEY_ID+"TEST",
53 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
54 |
55 | @pytest.mark.xfail(raises=TableAlreadyExists)
56 | def test_create_new_table():
57 | table_creation_status = db.create_table(
58 | table_name=table_schema.get("TableName"),
59 | key_schema=table_schema.get("KeySchema"),
60 | attribute_definitions=table_schema.get("AttributeDefinitions"),
61 | global_secondary_indexes=table_schema.get("GlobalSecondaryIndexes"),
62 | provisioned_throughput=table_schema.get("ProvisionedThroughput")
63 | )
64 | assert table_creation_status == True
65 | db.create_table(
66 | table_name=table_schema.get("TableName"),
67 | key_schema=table_schema.get("KeySchema"),
68 | attribute_definitions=table_schema.get("AttributeDefinitions"),
69 | global_secondary_indexes=table_schema.get("GlobalSecondaryIndexes"),
70 | provisioned_throughput=table_schema.get("ProvisionedThroughput")
71 | )
72 |
73 | @pytest.mark.xfail(raises=UnexpectedError)
74 | def test_get_all_table_name():
75 | table_names = db.read_all_table_names()
76 | assert len(table_names)>0
77 | db_wrong_credentials.read_all_table_names()
78 |
79 | @pytest.mark.xfail(raises=UnexpectedError)
80 | def test_create_new_item():
81 | item_creation_status = db.create_item(
82 | table_name=table_schema.get("TableName"),
83 | item={
84 | "company_name": "Google",
85 | "role_id": ITEM1_PARTITION_KEY,
86 | "role": "Software Engineer 1",
87 | "salary": "$1,50,531",
88 | "locations": ["Mountain View, California", "Austin, Texas", "Chicago, IL"],
89 | "yearly_hike_percent": 8,
90 | "benefits": set(["Internet, Medical, Edu reimbursements",
91 | "Health insurance",
92 | "Travel reimbursements"
93 | ]),
94 | "overall_review":{
95 | "overall_rating" : "4/5",
96 | "compensation_and_benefits": "3.9/5"
97 | }
98 | }
99 | )
100 | assert item_creation_status == True
101 | db_wrong_credentials.create_item(
102 | table_name=table_schema.get("TableName"),
103 | item={
104 | "company_name": "Google",
105 | "role_id": ITEM1_PARTITION_KEY,
106 | "role": "Software Engineer 1",
107 | "salary": "$1,50,531",
108 | "locations": ["Mountain View, California", "Austin, Texas", "Chicago, IL"],
109 | "yearly_hike_percent": 8,
110 | "benefits": set(["Internet, Medical, Edu reimbursements",
111 | "Health insurance",
112 | "Travel reimbursements"
113 | ]),
114 | "overall_review":{
115 | "overall_rating" : "4/5",
116 | "compensation_and_benefits": "3.9/5"
117 | }
118 | }
119 | )
120 |
121 | @pytest.mark.xfail(raises=ItemNotFound)
122 | def test_read_item():
123 | item = db.read_item(
124 | table_name=table_schema.get("TableName"),
125 | key={
126 | "company_name": "Google",
127 | "role_id": ITEM1_PARTITION_KEY
128 | }
129 | )
130 | assert item != None
131 | db.read_item(
132 | table_name=table_schema.get("TableName"),
133 | key={
134 | "company_name": "Airbnb",
135 | "role_id": ITEM1_PARTITION_KEY
136 | }
137 | )
138 |
139 | @pytest.mark.xfail(raises=UnexpectedError)
140 | def test_increase_attribute_value():
141 | increase_attribute_status = db.increase_attribute_value(
142 | table_name=table_schema.get("TableName"),
143 | key={
144 | "company_name": "Google",
145 | "role_id": ITEM1_PARTITION_KEY
146 | },
147 | attribute_name="yearly_hike_percent",
148 | increment_value=5
149 | )
150 | assert increase_attribute_status==True
151 | db_wrong_credentials.increase_attribute_value(
152 | table_name=table_schema.get("TableName"),
153 | key={
154 | "company_name": "Google",
155 | "role_id": ITEM1_PARTITION_KEY
156 | },
157 | attribute_name="yearly_hike_percent",
158 | increment_value=5
159 | )
160 |
161 | @pytest.mark.xfail(raises=UnexpectedError)
162 | def test_update_existing_attribute():
163 | item_update_status = db.update_item(
164 | table_name=table_schema.get("TableName"),
165 | key={
166 | "company_name": "Google",
167 | "role_id": ITEM1_PARTITION_KEY
168 | },
169 | attributes_to_update={
170 | 'role': 'Staff Software Engineer 2'
171 | }
172 | )
173 | assert item_update_status == True
174 | db_wrong_credentials.update_item(
175 | table_name=table_schema.get("TableName"),
176 | key={
177 | "company_name": "Google",
178 | "role_id": ITEM1_PARTITION_KEY
179 | },
180 | attributes_to_update={
181 | 'role': 'Staff Software Engineer 2'
182 | }
183 | )
184 |
185 | @pytest.mark.xfail(raises=UnexpectedError)
186 | def test_add_new_attribute():
187 | item_update_status = db.update_item(
188 | table_name=table_schema.get("TableName"),
189 | key={
190 | "company_name": "Google",
191 | "role_id": ITEM1_PARTITION_KEY
192 | },
193 | attributes_to_update={
194 | 'overall_review.yearly_bonus_percent': 12
195 | }
196 | )
197 | assert item_update_status == True
198 | db_wrong_credentials.update_item(
199 | table_name=table_schema.get("TableName"),
200 | key={
201 | "company_name": "Google",
202 | "role_id": ITEM1_PARTITION_KEY
203 | },
204 | attributes_to_update={
205 | 'overall_review.yearly_bonus_percent': 12
206 | }
207 | )
208 |
209 | @pytest.mark.xfail(raises=UnexpectedError)
210 | def test_add_attribute_to_list():
211 | item_update_status = db.update_item(
212 | table_name=table_schema.get("TableName"),
213 | key={
214 | "company_name": "Google",
215 | "role_id": ITEM1_PARTITION_KEY
216 | },
217 | attributes_to_update={
218 | 'locations': "Detroit, Michigan"
219 | },
220 | operation="ADD_ATTRIBUTE_TO_LIST"
221 | )
222 | assert item_update_status == True
223 | db_wrong_credentials.update_item(
224 | table_name=table_schema.get("TableName"),
225 | key={
226 | "company_name": "Google",
227 | "role_id": ITEM1_PARTITION_KEY
228 | },
229 | attributes_to_update={
230 | 'locations': "Detroit, Michigan"
231 | },
232 | operation="ADD_ATTRIBUTE_TO_LIST"
233 | )
234 |
235 | @pytest.mark.xfail(raises=UnexpectedError)
236 | def test_add_attributes_to_string_set():
237 | item_update_status = db.update_item(
238 | table_name=table_schema.get("TableName"),
239 | key={
240 | "company_name": "Google",
241 | "role_id": ITEM1_PARTITION_KEY
242 | },
243 | attributes_to_update={
244 | 'benefits': "Free Food"
245 | },
246 | operation="ADD_ATTRIBUTE_TO_STRING_SET"
247 | )
248 | assert item_update_status == True
249 | db_wrong_credentials.update_item(
250 | table_name=table_schema.get("TableName"),
251 | key={
252 | "company_name": "Google",
253 | "role_id": ITEM1_PARTITION_KEY
254 | },
255 | attributes_to_update={
256 | 'benefits': "Free Food"
257 | },
258 | operation="ADD_ATTRIBUTE_TO_STRING_SET"
259 | )
260 |
261 | @pytest.mark.xfail(raises=UnexpectedError)
262 | def test_delete_attribute_from_string_set():
263 | item_update_status = db.update_item(
264 | table_name=table_schema.get("TableName"),
265 | key={
266 | "company_name": "Google",
267 | "role_id": ITEM1_PARTITION_KEY
268 | },
269 | attributes_to_update={
270 | 'benefits': "Free Food"
271 | },
272 | operation="DELETE_ATTRIBUTE_FROM_STRING_SET"
273 | )
274 | assert item_update_status == True
275 | db_wrong_credentials.update_item(
276 | table_name=table_schema.get("TableName"),
277 | key={
278 | "company_name": "Google",
279 | "role_id": ITEM1_PARTITION_KEY
280 | },
281 | attributes_to_update={
282 | 'benefits': "Free Food"
283 | },
284 | operation="DELETE_ATTRIBUTE_FROM_STRING_SET"
285 | )
286 |
287 | @pytest.mark.xfail(raises=UnexpectedError)
288 | def test_delete_attribute_from_item():
289 | attribute_delete_status = db.delete_attribute(
290 | table_name=table_schema.get("TableName"),
291 | key={
292 | "company_name": "Google",
293 | "role_id": ITEM1_PARTITION_KEY
294 | },
295 | attribute_name="yearly_hike_percent")
296 | assert attribute_delete_status == True
297 | db_wrong_credentials.delete_attribute(
298 | table_name=table_schema.get("TableName"),
299 | key={
300 | "company_name": "Google",
301 | "role_id": ITEM1_PARTITION_KEY
302 | },
303 | attribute_name="yearly_hike_percent")
304 |
305 | @pytest.mark.xfail(raises=QueryFilterValidationFailed)
306 | def test_read_items_by_filter():
307 | item_creation_status = db.create_item(
308 | table_name=table_schema.get("TableName"),
309 | item={
310 | "company_name": "Google",
311 | "role_id": str(uuid.uuid4()),
312 | "role": "Software Architect",
313 | "salary": "$4,80,000",
314 | "locations": ["Mountain View, California"],
315 | "yearly_hike_percent": 13,
316 | "benefits": set(["Internet reimbursements"]),
317 | "overall_review":{
318 | "overall_rating" : "3/5",
319 | "compensation_and_benefits": "4.2/5"
320 | }
321 | }
322 | )
323 | assert item_creation_status == True
324 | items = db.read_items_by_filter(
325 | table_name=table_schema.get("TableName"),
326 | key_condition_expression=Key("company_name").eq("Google")
327 | )
328 | assert len(items)>0
329 | db.read_items_by_filter(
330 | table_name=table_schema.get("TableName"),
331 | key_condition_expression=Key("stock").eq("100$")
332 | )
333 |
334 | @pytest.mark.xfail(raises=UnexpectedError)
335 | def test_delete_item():
336 | delete_item_status = db.delete_item(
337 | table_name=table_schema.get("TableName"),
338 | key={
339 | "company_name": "Google",
340 | "role_id": ITEM1_PARTITION_KEY
341 | }
342 | )
343 | assert delete_item_status == True
344 | db_wrong_credentials.delete_item(
345 | table_name=table_schema.get("TableName"),
346 | key={
347 | "company_name": "Google",
348 | "role_id": ITEM1_PARTITION_KEY
349 | }
350 | )
351 |
352 | @pytest.mark.xfail(raises=TableNotFound)
353 | def test_delete_table():
354 | delete_table_status = db.delete_table(table_name=table_schema.get("TableName"))
355 | assert delete_table_status == True
356 | db.delete_table(table_name=table_schema.get("TableName"))
--------------------------------------------------------------------------------