├── .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 |

2 | 3 | LucidDynamodb 4 | 5 |

6 |

7 | A minimalistic wrapper to AWS DynamoDB 8 |

9 |

10 | 11 | 12 | 13 |

14 | 15 |

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Deployment 24 | 25 | 26 | Package version 27 | 28 | 29 | MIT License 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": "

\"LucidDynamodb\"

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")) --------------------------------------------------------------------------------