├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ └── codeql-analysis.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── LICENSE ├── NOTICE ├── README.md ├── THIRD-PARTY ├── docs ├── Makefile ├── make.bat └── source │ ├── _static │ └── 404.html │ ├── _templates │ ├── globaltoc.html │ └── layout.html │ ├── conf.py │ ├── guide │ └── quickstart.rst │ ├── index.rst │ └── reference │ ├── core │ ├── add_secondary_owner.rst │ ├── connect_to_ledger.rst │ ├── constants.rst │ ├── create_index.rst │ ├── create_ledger.rst │ ├── create_table.rst │ ├── delete_ledger.rst │ ├── deletion_protection.rst │ ├── deregister_drivers_license.rst │ ├── describe_journal_export.rst │ ├── describe_ledger.rst │ ├── export_journal.rst │ ├── find_vehicles.rst │ ├── get_block.rst │ ├── get_digest.rst │ ├── get_revision.rst │ ├── index.rst │ ├── insert_document.rst │ ├── insert_ion_types.rst │ ├── journal_s3_export_reader.rst │ ├── list_journal_exports.rst │ ├── list_ledgers.rst │ ├── list_tables.rst │ ├── occ_conflict_demo.rst │ ├── query_history.rst │ ├── redact_revision.rst │ ├── register_drivers_license.rst │ ├── renew_drivers_license.rst │ ├── scan_table.rst │ ├── tag_resource.rst │ ├── transfer_vehicle_ownership.rst │ ├── validate_qldb_hash_chain.rst │ └── verifier.rst │ ├── model │ └── sample_data.rst │ └── qldb │ ├── block_address.rst │ ├── index.rst │ ├── journal_block.rst │ └── qldb_string_utils.rst ├── pyqldbsamples ├── __init__.py ├── add_secondary_owner.py ├── connect_to_ledger.py ├── constants.py ├── create_index.py ├── create_ledger.py ├── create_table.py ├── delete_ledger.py ├── deletion_protection.py ├── deregister_drivers_license.py ├── describe_journal_export.py ├── describe_ledger.py ├── export_journal.py ├── find_vehicles.py ├── get_block.py ├── get_digest.py ├── get_revision.py ├── insert_document.py ├── insert_ion_types.py ├── journal_s3_export_reader.py ├── list_journal_exports.py ├── list_ledgers.py ├── list_tables.py ├── model │ ├── __init__.py │ └── sample_data.py ├── qldb │ ├── __init__.py │ ├── block_address.py │ ├── journal_block.py │ └── qldb_string_utils.py ├── query_history.py ├── redact_revision.py ├── register_drivers_license.py ├── renew_drivers_license.py ├── scan_table.py ├── tag_resource.py ├── transfer_vehicle_ownership.py ├── validate_qldb_hash_chain.py └── verifier.py ├── requirements-docs.txt ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── cleanup.py ├── conftest.py └── test_integration.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "pip" 4 | directory: "/" 5 | target-branch: "master" 6 | schedule: 7 | interval: "weekly" 8 | day: "tuesday" 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | # This value needs to match the build matrix configuration job total. 11 | job-total: 12 12 | 13 | permissions: 14 | id-token: write 15 | contents: read 16 | 17 | jobs: 18 | generate-unique-id: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Generate unique id 22 | id: unique_id 23 | run: echo "::set-output name=id::$RANDOM" 24 | outputs: 25 | id: ${{ steps.unique_id.outputs.id }} 26 | 27 | build: 28 | needs: [generate-unique-id] 29 | runs-on: ${{ matrix.os }} 30 | strategy: 31 | max-parallel: 6 32 | matrix : 33 | os: [ubuntu-latest, windows-2019, macos-latest] 34 | python-version: ['3.7', '3.8', '3.9', '3.10'] 35 | steps: 36 | - name: Configure AWS Credentials 37 | uses: aws-actions/configure-aws-credentials@v1 38 | with: 39 | aws-region: us-east-2 40 | role-to-assume: arn:aws:iam::264319671630:role/GitHubSamplesActionsOidc 41 | 42 | - uses: actions/checkout@v2 43 | - name: Set up Python ${{ matrix.python-version }} 44 | uses: actions/setup-python@v2 45 | with: 46 | python-version: ${{ matrix.python-version }} 47 | - name: Install dependencies 48 | run: | 49 | ls 50 | pip install -r requirements.txt 51 | pip install -e . 52 | - name: Lint with flake8 53 | run: | 54 | pip install flake8 55 | # stop the build if there are Python syntax errors or undefined names 56 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 57 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 58 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 59 | - name: Test with pytest 60 | id: test 61 | run: | 62 | echo "::set-output name=job-total::${{ strategy.job-total }}" 63 | pip install pytest 64 | pytest --ledger_suffix ${{ needs.generate-unique-id.outputs.id}}${{ strategy.job-index }} 65 | outputs: 66 | job-total: ${{ steps.test.outputs.job-total }} 67 | 68 | cleanup: 69 | # This job will always run 70 | if: ${{ always() && needs.generate-unique-id.outputs.id != '' }} 71 | runs-on: ubuntu-latest 72 | needs: [generate-unique-id, build] 73 | 74 | steps: 75 | - name: Configure AWS Credentials 76 | uses: aws-actions/configure-aws-credentials@v1 77 | with: 78 | aws-region: us-east-2 79 | role-to-assume: arn:aws:iam::264319671630:role/GitHubSamplesActionsOidc 80 | - uses: actions/checkout@v2 81 | - name: Set up Python ${{ matrix.python-version }} 82 | uses: actions/setup-python@v2 83 | with: 84 | python-version: 3.8 85 | - name: Install dependencies 86 | run: | 87 | ls 88 | pip install -r requirements.txt 89 | pip install -e . 90 | - name: Delete QLDB resources 91 | run: | 92 | cd tests 93 | 94 | # If the build job was cancelled by user, we will need to use the job-total global environment variable. 95 | if [ -z "${{ needs.build.outputs.job-total }}" ] 96 | then 97 | let total=${{env.job-total}} 98 | else 99 | let total=${{needs.build.outputs.job-total }} 100 | fi 101 | 102 | for ((i=0;i/dev/null 2>&1; echo $$?), 1) 28 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 29 | endif 30 | 31 | # Internal variables. 32 | PAPEROPT_a4 = -D latex_paper_size=a4 33 | PAPEROPT_letter = -D latex_paper_size=letter 34 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 35 | # the i18n builder cannot share the environment and doctrees with the others 36 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 37 | 38 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 39 | 40 | help: 41 | @echo "Please use \`make ' where is one of" 42 | @echo " html to make standalone HTML files" 43 | @echo " dirhtml to make HTML files named index.html in directories" 44 | @echo " singlehtml to make a single large HTML file" 45 | @echo " pickle to make pickle files" 46 | @echo " json to make JSON files" 47 | @echo " htmlhelp to make HTML files and a HTML help project" 48 | @echo " qthelp to make HTML files and a qthelp project" 49 | @echo " devhelp to make HTML files and a Devhelp project" 50 | @echo " epub to make an epub" 51 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 52 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 53 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 54 | @echo " text to make text files" 55 | @echo " man to make manual pages" 56 | @echo " texinfo to make Texinfo files" 57 | @echo " info to make Texinfo files and run them through makeinfo" 58 | @echo " gettext to make PO message catalogs" 59 | @echo " changes to make an overview of all changed/added/deprecated items" 60 | @echo " xml to make Docutils-native XML files" 61 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 62 | @echo " linkcheck to check all external links for integrity" 63 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 64 | 65 | clean: 66 | rm -rf $(BUILDDIR)/* 67 | 68 | html: 69 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 70 | @echo 71 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 72 | 73 | dirhtml: 74 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 75 | @echo 76 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 77 | 78 | singlehtml: 79 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 80 | @echo 81 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 82 | 83 | pickle: 84 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 85 | @echo 86 | @echo "Build finished; now you can process the pickle files." 87 | 88 | json: 89 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 90 | @echo 91 | @echo "Build finished; now you can process the JSON files." 92 | 93 | htmlhelp: 94 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 95 | @echo 96 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 97 | ".hhp project file in $(BUILDDIR)/htmlhelp." 98 | 99 | qthelp: 100 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 101 | @echo 102 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 103 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 104 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/test.qhcp" 105 | @echo "To view the help file:" 106 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/test.qhc" 107 | 108 | devhelp: 109 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 110 | @echo 111 | @echo "Build finished." 112 | @echo "To view the help file:" 113 | @echo "# mkdir -p $$HOME/.local/share/devhelp/test" 114 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/test" 115 | @echo "# devhelp" 116 | 117 | epub: 118 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 119 | @echo 120 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 121 | 122 | latex: 123 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 124 | @echo 125 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 126 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 127 | "(use \`make latexpdf' here to do that automatically)." 128 | 129 | latexpdf: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through pdflatex..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | latexpdfja: 136 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 137 | @echo "Running LaTeX files through platex and dvipdfmx..." 138 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 139 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 140 | 141 | text: 142 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 143 | @echo 144 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 145 | 146 | man: 147 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 148 | @echo 149 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 150 | 151 | texinfo: 152 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 153 | @echo 154 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 155 | @echo "Run \`make' in that directory to run these through makeinfo" \ 156 | "(use \`make info' here to do that automatically)." 157 | 158 | info: 159 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 160 | @echo "Running Texinfo files through makeinfo..." 161 | make -C $(BUILDDIR)/texinfo info 162 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 163 | 164 | gettext: 165 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 166 | @echo 167 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 168 | 169 | changes: 170 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 171 | @echo 172 | @echo "The overview file is in $(BUILDDIR)/changes." 173 | 174 | linkcheck: 175 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 176 | @echo 177 | @echo "Link check complete; look for any errors in the above output " \ 178 | "or in $(BUILDDIR)/linkcheck/output.txt." 179 | 180 | doctest: 181 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 182 | @echo "Testing of doctests in the sources finished, look at the " \ 183 | "results in $(BUILDDIR)/doctest/output.txt." 184 | 185 | xml: 186 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 187 | @echo 188 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 189 | 190 | pseudoxml: 191 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 192 | @echo 193 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 194 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | @ECHO OFF 18 | 19 | REM Command file for Sphinx documentation 20 | 21 | if "%SPHINXBUILD%" == "" ( 22 | set SPHINXBUILD=sphinx-build 23 | ) 24 | set BUILDDIR=build 25 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 26 | set I18NSPHINXOPTS=%SPHINXOPTS% source 27 | if NOT "%PAPER%" == "" ( 28 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 29 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 30 | ) 31 | 32 | if "%1" == "" goto help 33 | 34 | if "%1" == "help" ( 35 | :help 36 | echo.Please use `make ^` where ^ is one of 37 | echo. html to make standalone HTML files 38 | echo. dirhtml to make HTML files named index.html in directories 39 | echo. singlehtml to make a single large HTML file 40 | echo. pickle to make pickle files 41 | echo. json to make JSON files 42 | echo. htmlhelp to make HTML files and a HTML help project 43 | echo. qthelp to make HTML files and a qthelp project 44 | echo. devhelp to make HTML files and a Devhelp project 45 | echo. epub to make an epub 46 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 47 | echo. text to make text files 48 | echo. man to make manual pages 49 | echo. texinfo to make Texinfo files 50 | echo. gettext to make PO message catalogs 51 | echo. changes to make an overview over all changed/added/deprecated items 52 | echo. xml to make Docutils-native XML files 53 | echo. pseudoxml to make pseudoxml-XML files for display purposes 54 | echo. linkcheck to check all external links for integrity 55 | echo. doctest to run all doctests embedded in the documentation if enabled 56 | goto end 57 | ) 58 | 59 | if "%1" == "clean" ( 60 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 61 | del /q /s %BUILDDIR%\* 62 | goto end 63 | ) 64 | 65 | 66 | %SPHINXBUILD% 2> nul 67 | if errorlevel 9009 ( 68 | echo. 69 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 70 | echo.installed, then set the SPHINXBUILD environment variable to point 71 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 72 | echo.may add the Sphinx directory to PATH. 73 | echo. 74 | echo.If you don't have Sphinx installed, grab it from 75 | echo.http://sphinx-doc.org/ 76 | exit /b 1 77 | ) 78 | 79 | if "%1" == "html" ( 80 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 84 | goto end 85 | ) 86 | 87 | if "%1" == "dirhtml" ( 88 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 92 | goto end 93 | ) 94 | 95 | if "%1" == "singlehtml" ( 96 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 100 | goto end 101 | ) 102 | 103 | if "%1" == "pickle" ( 104 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can process the pickle files. 108 | goto end 109 | ) 110 | 111 | if "%1" == "json" ( 112 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 113 | if errorlevel 1 exit /b 1 114 | echo. 115 | echo.Build finished; now you can process the JSON files. 116 | goto end 117 | ) 118 | 119 | if "%1" == "htmlhelp" ( 120 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 121 | if errorlevel 1 exit /b 1 122 | echo. 123 | echo.Build finished; now you can run HTML Help Workshop with the ^ 124 | .hhp project file in %BUILDDIR%/htmlhelp. 125 | goto end 126 | ) 127 | 128 | if "%1" == "qthelp" ( 129 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 130 | if errorlevel 1 exit /b 1 131 | echo. 132 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 133 | .qhcp project file in %BUILDDIR%/qthelp, like this: 134 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\test.qhcp 135 | echo.To view the help file: 136 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\test.ghc 137 | goto end 138 | ) 139 | 140 | if "%1" == "devhelp" ( 141 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. 145 | goto end 146 | ) 147 | 148 | if "%1" == "epub" ( 149 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 153 | goto end 154 | ) 155 | 156 | if "%1" == "latex" ( 157 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 161 | goto end 162 | ) 163 | 164 | if "%1" == "latexpdf" ( 165 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 166 | cd %BUILDDIR%/latex 167 | make all-pdf 168 | cd %BUILDDIR%/.. 169 | echo. 170 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 171 | goto end 172 | ) 173 | 174 | if "%1" == "latexpdfja" ( 175 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 176 | cd %BUILDDIR%/latex 177 | make all-pdf-ja 178 | cd %BUILDDIR%/.. 179 | echo. 180 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 181 | goto end 182 | ) 183 | 184 | if "%1" == "text" ( 185 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The text files are in %BUILDDIR%/text. 189 | goto end 190 | ) 191 | 192 | if "%1" == "man" ( 193 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 197 | goto end 198 | ) 199 | 200 | if "%1" == "texinfo" ( 201 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 205 | goto end 206 | ) 207 | 208 | if "%1" == "gettext" ( 209 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 213 | goto end 214 | ) 215 | 216 | if "%1" == "changes" ( 217 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 218 | if errorlevel 1 exit /b 1 219 | echo. 220 | echo.The overview file is in %BUILDDIR%/changes. 221 | goto end 222 | ) 223 | 224 | if "%1" == "linkcheck" ( 225 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 226 | if errorlevel 1 exit /b 1 227 | echo. 228 | echo.Link check complete; look for any errors in the above output ^ 229 | or in %BUILDDIR%/linkcheck/output.txt. 230 | goto end 231 | ) 232 | 233 | if "%1" == "doctest" ( 234 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 235 | if errorlevel 1 exit /b 1 236 | echo. 237 | echo.Testing of doctests in the sources finished, look at the ^ 238 | results in %BUILDDIR%/doctest/output.txt. 239 | goto end 240 | ) 241 | 242 | if "%1" == "xml" ( 243 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 244 | if errorlevel 1 exit /b 1 245 | echo. 246 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 247 | goto end 248 | ) 249 | 250 | if "%1" == "pseudoxml" ( 251 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 252 | if errorlevel 1 exit /b 1 253 | echo. 254 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 255 | goto end 256 | ) 257 | 258 | :end 259 | -------------------------------------------------------------------------------- /docs/source/_static/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found 6 | 30 | 31 | 32 |

Page Not Found

33 |

Sorry, the page you requested could not be found.

34 | 35 | -------------------------------------------------------------------------------- /docs/source/_templates/globaltoc.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /docs/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "!layout.html" %} 2 | 3 | {%- block breadcrumbs %} 4 | {{ super() }} 5 | 6 | {%- endblock %} 7 | 8 | 9 | {%- block footer %} 10 | {{ super() }} 11 | 12 | 20 | {%- endblock %} 21 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | #!/usr/bin/env python3 18 | # -*- coding: utf-8 -*- 19 | # 20 | # AmazonQLDB Sample App documentation build configuration file, created by 21 | # sphinx-quickstart on Mon Oct 7 18:23:00 2019. 22 | # 23 | # This file is execfile()d with the current directory set to its 24 | # containing dir. 25 | # 26 | # Note that not all possible configuration values are present in this 27 | # autogenerated file. 28 | # 29 | # All configuration values have a default; values that are commented out 30 | # serve to show the default. 31 | 32 | 33 | import os 34 | 35 | import pyqldbsamples 36 | 37 | # -- General configuration ----------------------------------------------------- 38 | 39 | # If your documentation needs a minimal Sphinx version, state it here. 40 | #needs_sphinx = '1.0' 41 | 42 | # Add any Sphinx extension module names here, as strings. They can be extensions 43 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 44 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode'] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ['_templates'] 48 | 49 | # The suffix of source filenames. 50 | source_suffix = '.rst' 51 | 52 | # The encoding of source files. 53 | #source_encoding = 'utf-8-sig' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # General information about the project. 59 | project = 'AmazonQLDB Python Sample App Docs' 60 | copyright = '2019, Amazon Web Services, Inc' 61 | 62 | # The version info for the project you're documenting, acts as replacement for 63 | # |version| and |release|, also used in various other places throughout the 64 | # built documents. 65 | # 66 | # The short X.Y version. 67 | version = pyqldbsamples.__version__ 68 | # The full version, including alpha/beta/rc tags. 69 | release = pyqldbsamples.__version__ 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | #language = None 74 | 75 | # There are two options for replacing |today|: either, you set today to some 76 | # non-false value, then it is used: 77 | #today = '' 78 | # Else, today_fmt is used as the format for a strftime call. 79 | #today_fmt = '%B %d, %Y' 80 | 81 | # List of patterns, relative to source directory, that match files and 82 | # directories to ignore when looking for source files. 83 | exclude_patterns = [] 84 | 85 | # The reST default role (used for this markup: `text`) to use for all documents. 86 | #default_role = None 87 | 88 | # If true, '()' will be appended to :func: etc. cross-reference text. 89 | #add_function_parentheses = True 90 | 91 | # If true, the current module name will be prepended to all description 92 | # unit titles (such as .. function::). 93 | # add_module_names = True 94 | 95 | # If true, sectionauthor and moduleauthor directives will be shown in the 96 | # output. They are ignored by default. 97 | #show_authors = False 98 | 99 | # The name of the Pygments (syntax highlighting) style to use. 100 | pygments_style = 'sphinx' 101 | 102 | # A list of ignored prefixes for module index sorting. 103 | #modindex_common_prefix = [] 104 | 105 | 106 | import guzzle_sphinx_theme 107 | 108 | extensions.append("guzzle_sphinx_theme") 109 | html_translator_class = 'guzzle_sphinx_theme.HTMLTranslator' 110 | html_theme_path = guzzle_sphinx_theme.html_theme_path() 111 | html_theme = 'guzzle_sphinx_theme' 112 | # Guzzle theme options (see theme.conf for more information) 113 | 114 | html_theme_options = { 115 | # hack to add tracking 116 | "google_analytics_account": os.getenv('TRACKING', False), 117 | "base_url": "http://docs.aws.amazon.com/aws-sdk-php/guide/latest/" 118 | } 119 | 120 | # -- Options for HTML output --------------------------------------------------- 121 | 122 | # The theme to use for HTML and HTML Help pages. See the documentation for 123 | # a list of builtin themes. 124 | #html_theme = 'default' 125 | 126 | # Theme options are theme-specific and customize the look and feel of a theme 127 | # further. For a list of options available for each theme, see the 128 | # documentation. 129 | #html_theme_options = {} 130 | 131 | # Add any paths that contain custom themes here, relative to this directory. 132 | #html_theme_path = [] 133 | 134 | # The name for this set of Sphinx documents. If None, it defaults to 135 | # " v documentation". 136 | #html_title = None 137 | 138 | # A shorter title for the navigation bar. Default is the same as html_title. 139 | #html_short_title = None 140 | 141 | # The name of an image file (relative to this directory) to place at the top 142 | # of the sidebar. 143 | #html_logo = None 144 | 145 | # The name of an image file (within the static path) to use as favicon of the 146 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 147 | # pixels large. 148 | #html_favicon = None 149 | 150 | # Add any paths that contain custom static files (such as style sheets) here, 151 | # relative to this directory. They are copied after the builtin static files, 152 | # so a file named "default.css" will overwrite the builtin "default.css". 153 | html_static_path = ['_static'] 154 | 155 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 156 | # using the given strftime format. 157 | #html_last_updated_fmt = '%b %d, %Y' 158 | 159 | # If true, SmartyPants will be used to convert quotes and dashes to 160 | # typographically correct entities. 161 | #html_use_smartypants = True 162 | 163 | # Custom sidebar templates, maps document names to template names. 164 | html_show_sourcelink = False 165 | html_sidebars = { 166 | '**': ['logo-text.html', 167 | 'globaltoc.html', 168 | 'searchbox.html'] 169 | } 170 | 171 | # Additional templates that should be rendered to pages, maps page names to 172 | # template names. 173 | #html_additional_pages = {} 174 | 175 | # If false, no module index is generated. 176 | #html_domain_indices = True 177 | 178 | # If false, no index is generated. 179 | #html_use_index = True 180 | 181 | # If true, the index is split into individual pages for each letter. 182 | #html_split_index = False 183 | 184 | # If true, links to the reST sources are added to the pages. 185 | # html_show_sourcelink = True 186 | 187 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 188 | #html_show_sphinx = True 189 | 190 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 191 | #html_show_copyright = True 192 | 193 | # If true, an OpenSearch description file will be output, and all pages will 194 | # contain a tag referring to it. The value of this option must be the 195 | # base URL from which the finished HTML is served. 196 | #html_use_opensearch = '' 197 | 198 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 199 | #html_file_suffix = None 200 | 201 | # Output file base name for HTML help builder. 202 | htmlhelp_basename = 'AmazonQLDBPythonSampleAppDocs' 203 | 204 | 205 | # -- Options for LaTeX output -------------------------------------------------- 206 | 207 | latex_elements = { 208 | # The paper size ('letterpaper' or 'a4paper'). 209 | #'papersize': 'letterpaper', 210 | 211 | # The font size ('10pt', '11pt' or '12pt'). 212 | #'pointsize': '10pt', 213 | 214 | # Additional stuff for the LaTeX preamble. 215 | #'preamble': '', 216 | } 217 | 218 | # Grouping the document tree into LaTeX files. List of tuples 219 | # (source start file, target name, title, author, documentclass [howto/manual]). 220 | latex_documents = [ 221 | ('index', 'AmazonQLDBPythonSampleApp.tex', 'AmazonQLDB Python Sample App Documentation', 222 | 'Amazon.com, Inc.', 'manual'), 223 | ] 224 | 225 | # The name of an image file (relative to this directory) to place at the top of 226 | # the title page. 227 | #latex_logo = None 228 | 229 | # For "manual" documents, if this is true, then toplevel headings are parts, 230 | # not chapters. 231 | #latex_use_parts = False 232 | 233 | # If true, show page references after internal links. 234 | #latex_show_pagerefs = False 235 | 236 | # If true, show URL addresses after external links. 237 | #latex_show_urls = False 238 | 239 | # Documents to append as an appendix to all manuals. 240 | #latex_appendices = [] 241 | 242 | # If false, no module index is generated. 243 | #latex_domain_indices = True 244 | 245 | 246 | # -- Options for manual page output -------------------------------------------- 247 | 248 | # One entry per manual page. List of tuples 249 | # (source start file, name, description, authors, manual section). 250 | man_pages = [ 251 | ('index', 'AmazonQLDBPythonSampleApp', 'AmazonQLDB Python Sample App Documentation', 252 | ['Amazon.com, Inc.'], 1) 253 | ] 254 | 255 | # If true, show URL addresses after external links. 256 | #man_show_urls = False 257 | 258 | 259 | # -- Options for Texinfo output ------------------------------------------------ 260 | 261 | # Grouping the document tree into Texinfo files. List of tuples 262 | # (source start file, target name, title, author, 263 | # dir menu entry, description, category) 264 | texinfo_documents = [ 265 | ('index', 'AmazonQLDBPythonSampleApp', 'AmazonQLDB Python Sample App Documentation', 266 | 'Amazon.com, Inc.', 'AmazonQLDB', 'One line description of project.', 267 | 'Miscellaneous'), 268 | ] 269 | 270 | # Documents to append as an appendix to all manuals. 271 | #texinfo_appendices = [] 272 | 273 | # If false, no module index is generated. 274 | #texinfo_domain_indices = True 275 | 276 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 277 | #texinfo_show_urls = 'footnote' 278 | 279 | autoclass_content = 'both' -------------------------------------------------------------------------------- /docs/source/guide/quickstart.rst: -------------------------------------------------------------------------------- 1 | .. _guide_quickstart: 2 | 3 | Quickstart 4 | ========== 5 | Getting started with AmazonQLDB sample application is easy, but requires a few steps. 6 | 7 | Installation 8 | ------------ 9 | Install the Python AmazonQLDB driver and other dependencies via :command:`pip`:: 10 | 11 | pip install -r requirements.txt 12 | 13 | Configuration 14 | ------------- 15 | Before you can begin running the sample application, you should set up authentication 16 | credentials. Credentials for your AWS account can be found in the 17 | `IAM Console `_. You can 18 | create or use an existing user. Go to manage access keys and 19 | generate a new set of keys. 20 | 21 | If you have the `AWS CLI `_ 22 | installed, then you can use it to configure your credentials file:: 23 | 24 | aws configure 25 | 26 | Alternatively, you can create the credential file yourself. By default, 27 | its location is at ``~/.aws/credentials``:: 28 | 29 | [default] 30 | aws_access_key_id = YOUR_ACCESS_KEY 31 | aws_secret_access_key = YOUR_SECRET_KEY 32 | 33 | You may also want to set a default region. This can be done in the 34 | configuration file. By default, its location is at ``~/.aws/config``:: 35 | 36 | [default] 37 | region=us-east-1 38 | 39 | Alternatively, you can pass a ``region_name`` when creating the driver. 40 | 41 | This sets up credentials for the default profile as well as a default 42 | region to use when creating connections. 43 | 44 | Using Sample Application 45 | ------------------------ 46 | 47 | The sample code creates a ledger with tables and indexes, and inserts some documents into those tables, 48 | among other things. Each of the files in the sample app can be run in the following way:: 49 | 50 | python create_ledger.py 51 | 52 | The above example will build the CreateLedger class with the necessary dependencies and create a ledger named: 53 | ``vehicle-registration``. You may run other examples after creating a ledger. 54 | 55 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. AmazonQLDB Sample App documentation master file, created by 2 | sphinx-quickstart on Mon Oct 7 18:23:00 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | AmazonQLDB Python Sample App Documentation 7 | =================================== 8 | This sample app demonstrates several uses of Amazon Quantum Ledger Database (QLDB). 9 | 10 | 11 | Quickstart 12 | ---------- 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | guide/quickstart 18 | 19 | 20 | API Reference 21 | ------------- 22 | 23 | Core 24 | ~~~~ 25 | 26 | .. toctree:: 27 | :maxdepth: 3 28 | 29 | reference/core/index 30 | 31 | Model 32 | ~~~~~ 33 | 34 | .. toctree:: 35 | :maxdepth: 3 36 | 37 | reference/model/sample_data 38 | 39 | QLDB 40 | ~~~~ 41 | 42 | .. toctree:: 43 | :maxdepth: 3 44 | 45 | reference/qldb/index 46 | 47 | 48 | Indices and tables 49 | ================== 50 | 51 | * :ref:`genindex` 52 | * :ref:`modindex` 53 | * :ref:`search` 54 | 55 | -------------------------------------------------------------------------------- /docs/source/reference/core/add_secondary_owner.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Add Secondary Owner Reference 3 | ============================= 4 | 5 | .. automodule:: pyqldbsamples.add_secondary_owner 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/connect_to_ledger.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Connect To Ledger Reference 3 | ============================= 4 | 5 | .. automodule:: pyqldbsamples.connect_to_ledger 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/constants.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Constants Reference 3 | =================== 4 | 5 | .. automodule:: pyqldbsamples.constants 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/create_index.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Create Index Reference 3 | ====================== 4 | 5 | .. automodule:: pyqldbsamples.create_index 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/create_ledger.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Create Ledger Reference 3 | ======================= 4 | 5 | .. automodule:: pyqldbsamples.create_ledger 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/create_table.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Create Table Reference 3 | ====================== 4 | 5 | .. automodule:: pyqldbsamples.create_table 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/delete_ledger.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Delete Ledger Reference 3 | ======================= 4 | 5 | .. automodule:: pyqldbsamples.delete_ledger 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/deletion_protection.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Deletion Protection Reference 3 | ============================= 4 | 5 | .. automodule:: pyqldbsamples.deletion_protection 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/core/deregister_drivers_license.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Deregister Drivers License Reference 3 | ==================================== 4 | 5 | .. automodule:: pyqldbsamples.deregister_drivers_license 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/describe_journal_export.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | Describe Journal Export Reference 3 | ================================= 4 | 5 | .. automodule:: pyqldbsamples.describe_journal_export 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/describe_ledger.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Describe Ledger Reference 3 | ========================= 4 | 5 | .. automodule:: pyqldbsamples.describe_ledger 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/export_journal.rst: -------------------------------------------------------------------------------- 1 | ======================== 2 | Export Journal Reference 3 | ======================== 4 | 5 | .. automodule:: pyqldbsamples.export_journal 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/find_vehicles.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Find Vehicles Reference 3 | ======================= 4 | 5 | .. automodule:: pyqldbsamples.find_vehicles 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/get_block.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Get Block Reference 3 | =================== 4 | 5 | .. automodule:: pyqldbsamples.get_block 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/get_digest.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | Get Digest Reference 3 | ==================== 4 | 5 | .. automodule:: pyqldbsamples.get_digest 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/get_revision.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Get Revision Reference 3 | ====================== 4 | 5 | .. automodule:: pyqldbsamples.get_revision 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Core References 3 | =============== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :titlesonly: 8 | :glob: 9 | 10 | * 11 | -------------------------------------------------------------------------------- /docs/source/reference/core/insert_document.rst: -------------------------------------------------------------------------------- 1 | ========================= 2 | Insert Document Reference 3 | ========================= 4 | 5 | .. automodule:: pyqldbsamples.insert_document 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/insert_ion_types.rst: -------------------------------------------------------------------------------- 1 | ========================== 2 | Insert Ion Types Reference 3 | ========================== 4 | 5 | .. automodule:: pyqldbsamples.insert_ion_types 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/journal_s3_export_reader.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | Journal S3 Export Reader Reference 3 | ================================== 4 | 5 | .. automodule:: pyqldbsamples.journal_s3_export_reader 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/list_journal_exports.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | List Journal Exports Reference 3 | ============================== 4 | 5 | .. automodule:: pyqldbsamples.list_journal_exports 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/list_ledgers.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | List Ledgers Reference 3 | ====================== 4 | 5 | .. automodule:: pyqldbsamples.list_ledgers 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/list_tables.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | List Tables Reference 3 | ===================== 4 | 5 | .. automodule:: pyqldbsamples.list_tables 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/occ_conflict_demo.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | OCC Conflict Demo Reference 3 | =========================== 4 | 5 | .. automodule:: pyqldbsamples.occ_conflict_demo 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/query_history.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Query History Reference 3 | ======================= 4 | 5 | .. automodule:: pyqldbsamples.query_history 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/redact_revision.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Redact Revision Reference 3 | ======================= 4 | 5 | .. automodule:: pyqldbsamples.redact_revision 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/register_drivers_license.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | Register Drivers License Reference 3 | ================================== 4 | 5 | .. automodule:: pyqldbsamples.register_drivers_license 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/renew_drivers_license.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | Renew Drivers License Reference 3 | =============================== 4 | 5 | .. automodule:: pyqldbsamples.renew_drivers_license 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/scan_table.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Scan Table License Reference 3 | ============================ 4 | 5 | .. automodule:: pyqldbsamples.scan_table 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/tag_resource.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | Tag Resource Reference 3 | ====================== 4 | 5 | .. automodule:: pyqldbsamples.tag_resource 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/transfer_vehicle_ownership.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Transfer Vehicle Ownership Reference 3 | ==================================== 4 | 5 | .. automodule:: pyqldbsamples.transfer_vehicle_ownership 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/validate_qldb_hash_chain.rst: -------------------------------------------------------------------------------- 1 | ================================== 2 | Validate QLDB Hash Chain Reference 3 | ================================== 4 | 5 | .. automodule:: pyqldbsamples.validate_qldb_hash_chain 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/core/verifier.rst: -------------------------------------------------------------------------------- 1 | ================== 2 | Verifier Reference 3 | ================== 4 | 5 | .. automodule:: pyqldbsamples.verifier 6 | :members: 7 | :undoc-members: 8 | -------------------------------------------------------------------------------- /docs/source/reference/model/sample_data.rst: -------------------------------------------------------------------------------- 1 | ==================== 2 | SampleData Reference 3 | ==================== 4 | 5 | .. automodule:: pyqldbsamples.model.sample_data 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/qldb/block_address.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | Block Address Reference 3 | ======================= 4 | 5 | .. automodule:: pyqldbsamples.qldb.block_address 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/qldb/index.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | QLDB References 3 | =============== 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :titlesonly: 8 | :glob: 9 | 10 | * 11 | -------------------------------------------------------------------------------- /docs/source/reference/qldb/journal_block.rst: -------------------------------------------------------------------------------- 1 | ====================== 2 | JournalBlock Reference 3 | ====================== 4 | 5 | .. automodule:: pyqldbsamples.qldb.journal_block 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /docs/source/reference/qldb/qldb_string_utils.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | QLDB String Utils Reference 3 | =========================== 4 | 5 | .. automodule:: pyqldbsamples.qldb.qldb_string_utils 6 | :members: 7 | :undoc-members: 8 | 9 | -------------------------------------------------------------------------------- /pyqldbsamples/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | __version__ = '2.0.0' 18 | -------------------------------------------------------------------------------- /pyqldbsamples/add_secondary_owner.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.model.sample_data import to_ion_struct, get_document_ids, print_result, SampleData, \ 22 | convert_object_to_ion 23 | from pyqldbsamples.constants import Constants 24 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | 29 | 30 | def get_document_id_by_gov_id(driver, government_id): 31 | """ 32 | Find a driver's person ID using the given government ID. 33 | 34 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 35 | :param driver: An instance of the QldbDriver class. 36 | 37 | :type government_id: str 38 | :param government_id: A driver's government ID. 39 | 40 | :rtype: list 41 | :return: A list of document IDs. 42 | """ 43 | logger.info("Finding secondary owner's person ID using given government ID: {}.".format(government_id)) 44 | return driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME, 'GovId', 45 | government_id)) 46 | 47 | 48 | def is_secondary_owner_for_vehicle(driver, vin, secondary_owner_id): 49 | """ 50 | Check whether a secondary owner has already been registered for the given VIN. 51 | 52 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 53 | :param driver: An instance of the QldbDriver class. 54 | 55 | :type vin: str 56 | :param vin: VIN of the vehicle to query. 57 | 58 | :type secondary_owner_id: str 59 | :param secondary_owner_id: The secondary owner's person ID. 60 | 61 | :rtype: bool 62 | :return: If the driver has already been registered. 63 | """ 64 | logger.info('Finding secondary owners for vehicle with VIN: {}...'.format(vin)) 65 | query = 'SELECT Owners.SecondaryOwners FROM VehicleRegistration AS v WHERE v.VIN = ?' 66 | rows = driver.execute_lambda(lambda executor: executor.execute_statement(query, convert_object_to_ion(vin))) 67 | 68 | for row in rows: 69 | secondary_owners = row.get('SecondaryOwners') 70 | person_ids = map(lambda owner: owner.get('PersonId').text, secondary_owners) 71 | if secondary_owner_id in person_ids: 72 | return True 73 | return False 74 | 75 | 76 | def add_secondary_owner_for_vin(driver, vin, parameter): 77 | """ 78 | Add a secondary owner into `VehicleRegistration` table for a particular VIN. 79 | 80 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 81 | :param driver: An instance of the QldbDriver class. 82 | 83 | :type vin: str 84 | :param vin: VIN of the vehicle to add a secondary owner for. 85 | 86 | :type parameter: :py:class:`amazon.ion.simple_types.IonPyValue` 87 | :param parameter: The Ion value or Python native type that is convertible to Ion for filling in parameters of the 88 | statement. 89 | """ 90 | logger.info('Inserting secondary owner for vehicle with VIN: {}...'.format(vin)) 91 | statement = "FROM VehicleRegistration AS v WHERE v.VIN = ? INSERT INTO v.Owners.SecondaryOwners VALUE ?" 92 | 93 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement, convert_object_to_ion(vin), 94 | parameter)) 95 | logger.info('VehicleRegistration Document IDs which had secondary owners added: ') 96 | print_result(cursor) 97 | 98 | 99 | def register_secondary_owner(driver, vin, gov_id): 100 | """ 101 | Register a secondary owner for a vehicle if they are not already registered. 102 | 103 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 104 | :param driver: An instance of the QldbDriver class. 105 | 106 | :type vin: str 107 | :param vin: VIN of the vehicle to register a secondary owner for. 108 | 109 | :type gov_id: str 110 | :param gov_id: The government ID of the owner. 111 | """ 112 | logger.info('Finding the secondary owners for vehicle with VIN: {}.'.format(vin)) 113 | 114 | document_ids = get_document_id_by_gov_id(driver, gov_id) 115 | 116 | for document_id in document_ids: 117 | if is_secondary_owner_for_vehicle(driver, vin, document_id): 118 | logger.info('Person with ID {} has already been added as a secondary owner of this vehicle.'.format(gov_id)) 119 | else: 120 | add_secondary_owner_for_vin(driver, vin, to_ion_struct('PersonId', document_id)) 121 | 122 | 123 | def main(ledger_name=Constants.LEDGER_NAME): 124 | """ 125 | Finds and adds secondary owners for a vehicle. 126 | """ 127 | vin = SampleData.VEHICLE[1]['VIN'] 128 | gov_id = SampleData.PERSON[0]['GovId'] 129 | try: 130 | with create_qldb_driver(ledger_name) as driver: 131 | register_secondary_owner(driver, vin, gov_id) 132 | logger.info('Secondary owners successfully updated.') 133 | except Exception as e: 134 | logger.exception('Error adding secondary owner.') 135 | raise e 136 | 137 | 138 | if __name__ == '__main__': 139 | main() 140 | -------------------------------------------------------------------------------- /pyqldbsamples/connect_to_ledger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from botocore.exceptions import ClientError 22 | 23 | from pyqldb.driver.qldb_driver import QldbDriver 24 | from pyqldbsamples.constants import Constants 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | 29 | 30 | def create_qldb_driver(ledger_name=Constants.LEDGER_NAME, region_name=None, endpoint_url=None, boto3_session=None): 31 | """ 32 | Create a QLDB driver for executing transactions. 33 | 34 | :type ledger_name: str 35 | :param ledger_name: The QLDB ledger name. 36 | 37 | :type region_name: str 38 | :param region_name: See [1]. 39 | 40 | :type endpoint_url: str 41 | :param endpoint_url: See [1]. 42 | 43 | :type boto3_session: :py:class:`boto3.session.Session` 44 | :param boto3_session: The boto3 session to create the client with (see [1]). 45 | 46 | :rtype: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 47 | :return: A QLDB driver object. 48 | 49 | [1]: `Boto3 Session.client Reference `. 50 | """ 51 | qldb_driver = QldbDriver(ledger_name=ledger_name, region_name=region_name, endpoint_url=endpoint_url, 52 | boto3_session=boto3_session) 53 | return qldb_driver 54 | 55 | 56 | def main(ledger_name=Constants.LEDGER_NAME): 57 | """ 58 | Connect to a given ledger using default settings. 59 | """ 60 | try: 61 | with create_qldb_driver(ledger_name) as driver: 62 | logger.info('Listing table names ') 63 | for table in driver.list_tables(): 64 | logger.info(table) 65 | except ClientError as ce: 66 | logger.exception('Unable to list tables.') 67 | raise ce 68 | 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /pyqldbsamples/constants.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | 18 | class Constants: 19 | """ 20 | Constant values used throughout this tutorial. 21 | """ 22 | LEDGER_NAME = "vehicle-registration" 23 | 24 | VEHICLE_REGISTRATION_TABLE_NAME = "VehicleRegistration" 25 | VEHICLE_TABLE_NAME = "Vehicle" 26 | PERSON_TABLE_NAME = "Person" 27 | DRIVERS_LICENSE_TABLE_NAME = "DriversLicense" 28 | 29 | LICENSE_NUMBER_INDEX_NAME = "LicenseNumber" 30 | GOV_ID_INDEX_NAME = "GovId" 31 | VEHICLE_VIN_INDEX_NAME = "VIN" 32 | LICENSE_PLATE_NUMBER_INDEX_NAME = "LicensePlateNumber" 33 | PERSON_ID_INDEX_NAME = "PersonId" 34 | 35 | JOURNAL_EXPORT_S3_BUCKET_NAME_PREFIX = "qldb-tutorial-journal-export" 36 | USER_TABLES = "information_schema.user_tables" 37 | S3_BUCKET_ARN_TEMPLATE = "arn:aws:s3:::" 38 | LEDGER_NAME_WITH_TAGS = "tags" 39 | 40 | RETRY_LIMIT = 4 41 | -------------------------------------------------------------------------------- /pyqldbsamples/create_index.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.constants import Constants 22 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 23 | 24 | logger = getLogger(__name__) 25 | basicConfig(level=INFO) 26 | 27 | 28 | def create_index(driver, table_name, index_attribute): 29 | """ 30 | Create an index for a particular table. 31 | 32 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 33 | :param driver: An instance of the QldbDriver class. 34 | 35 | :type table_name: str 36 | :param table_name: Name of the table to add indexes for. 37 | 38 | :type index_attribute: str 39 | :param index_attribute: Index to create on a single attribute. 40 | 41 | :rtype: int 42 | :return: The number of changes to the database. 43 | """ 44 | logger.info("Creating index on '{}'...".format(index_attribute)) 45 | statement = 'CREATE INDEX on {} ({})'.format(table_name, index_attribute) 46 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement)) 47 | return len(list(cursor)) 48 | 49 | 50 | def main(ledger_name=Constants.LEDGER_NAME): 51 | """ 52 | Create indexes on tables in a particular ledger. 53 | """ 54 | logger.info('Creating indexes on all tables...') 55 | try: 56 | with create_qldb_driver(ledger_name) as driver: 57 | create_index(driver, Constants.PERSON_TABLE_NAME, Constants.GOV_ID_INDEX_NAME) 58 | create_index(driver, Constants.VEHICLE_TABLE_NAME, Constants.VEHICLE_VIN_INDEX_NAME) 59 | create_index(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME, Constants.LICENSE_PLATE_NUMBER_INDEX_NAME) 60 | create_index(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME, Constants.VEHICLE_VIN_INDEX_NAME) 61 | create_index(driver, Constants.DRIVERS_LICENSE_TABLE_NAME, Constants.PERSON_ID_INDEX_NAME) 62 | create_index(driver, Constants.DRIVERS_LICENSE_TABLE_NAME, Constants.LICENSE_NUMBER_INDEX_NAME) 63 | logger.info('Indexes created successfully.') 64 | except Exception as e: 65 | logger.exception('Unable to create indexes.') 66 | raise e 67 | 68 | 69 | if __name__ == '__main__': 70 | main() 71 | -------------------------------------------------------------------------------- /pyqldbsamples/create_ledger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | from time import sleep 21 | 22 | from boto3 import client 23 | 24 | from pyqldbsamples.constants import Constants 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | qldb_client = client('qldb') 29 | 30 | LEDGER_CREATION_POLL_PERIOD_SEC = 10 31 | ACTIVE_STATE = "ACTIVE" 32 | 33 | 34 | def create_ledger(name): 35 | """ 36 | Create a new ledger with the specified name. 37 | 38 | :type name: str 39 | :param name: Name for the ledger to be created. 40 | 41 | :rtype: dict 42 | :return: Result from the request. 43 | """ 44 | logger.info("Let's create the ledger named: {}...".format(name)) 45 | result = qldb_client.create_ledger(Name=name, PermissionsMode='ALLOW_ALL') 46 | logger.info('Success. Ledger state: {}.'.format(result.get('State'))) 47 | return result 48 | 49 | 50 | def wait_for_active(name): 51 | """ 52 | Wait for the newly created ledger to become active. 53 | 54 | :type name: str 55 | :param name: The ledger to check on. 56 | 57 | :rtype: dict 58 | :return: Result from the request. 59 | """ 60 | logger.info('Waiting for ledger to become active...') 61 | while True: 62 | result = qldb_client.describe_ledger(Name=name) 63 | if result.get('State') == ACTIVE_STATE: 64 | logger.info('Success. Ledger is active and ready to use.') 65 | return result 66 | logger.info('The ledger is still creating. Please wait...') 67 | sleep(LEDGER_CREATION_POLL_PERIOD_SEC) 68 | 69 | 70 | def main(ledger_name=Constants.LEDGER_NAME): 71 | """ 72 | Create a ledger and wait for it to be active. 73 | """ 74 | try: 75 | create_ledger(ledger_name) 76 | wait_for_active(ledger_name) 77 | except Exception as e: 78 | logger.exception('Unable to create the ledger!') 79 | raise e 80 | 81 | 82 | if __name__ == '__main__': 83 | main() 84 | -------------------------------------------------------------------------------- /pyqldbsamples/create_table.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.constants import Constants 22 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 23 | 24 | logger = getLogger(__name__) 25 | basicConfig(level=INFO) 26 | 27 | 28 | def create_table(driver, table_name): 29 | """ 30 | Create a table with the specified name. 31 | 32 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 33 | :param driver: An instance of the QldbDriver class. 34 | 35 | :type table_name: str 36 | :param table_name: Name of the table to create. 37 | 38 | :rtype: int 39 | :return: The number of changes to the database. 40 | """ 41 | logger.info("Creating the '{}' table...".format(table_name)) 42 | statement = 'CREATE TABLE {}'.format(table_name) 43 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement)) 44 | logger.info('{} table created successfully.'.format(table_name)) 45 | return len(list(cursor)) 46 | 47 | 48 | def main(ledger_name=Constants.LEDGER_NAME): 49 | """ 50 | Create registrations, vehicles, owners, and licenses tables. 51 | """ 52 | try: 53 | with create_qldb_driver(ledger_name) as driver: 54 | create_table(driver, Constants.DRIVERS_LICENSE_TABLE_NAME) 55 | create_table(driver, Constants.PERSON_TABLE_NAME) 56 | create_table(driver, Constants.VEHICLE_TABLE_NAME) 57 | create_table(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME) 58 | logger.info('Tables created successfully.') 59 | except Exception as e: 60 | logger.exception('Errors creating tables.') 61 | raise e 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /pyqldbsamples/delete_ledger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | from time import sleep 21 | 22 | from boto3 import client 23 | 24 | from pyqldbsamples.constants import Constants 25 | from pyqldbsamples.describe_ledger import describe_ledger 26 | 27 | logger = getLogger(__name__) 28 | basicConfig(level=INFO) 29 | qldb_client = client('qldb') 30 | 31 | LEDGER_DELETION_POLL_PERIOD_SEC = 20 32 | 33 | 34 | def delete_ledger(ledger_name): 35 | """ 36 | Send a request to QLDB to delete the specified ledger. 37 | 38 | :type ledger_name: str 39 | :param ledger_name: Name for the ledger to be deleted. 40 | 41 | :rtype: dict 42 | :return: Result from the request. 43 | """ 44 | logger.info('Attempting to delete the ledger with name: {}...'.format(ledger_name)) 45 | result = qldb_client.delete_ledger(Name=ledger_name) 46 | logger.info('Success.') 47 | return result 48 | 49 | 50 | def wait_for_deleted(ledger_name): 51 | """ 52 | Wait for the ledger to be deleted. 53 | 54 | :type ledger_name: str 55 | :param ledger_name: The ledger to check on. 56 | """ 57 | logger.info('Waiting for the ledger to be deleted...') 58 | while True: 59 | try: 60 | describe_ledger(ledger_name) 61 | logger.info('The ledger is still being deleted. Please wait...') 62 | sleep(LEDGER_DELETION_POLL_PERIOD_SEC) 63 | except qldb_client.exceptions.ResourceNotFoundException: 64 | logger.info('Success. The ledger is deleted.') 65 | break 66 | 67 | 68 | def set_deletion_protection(ledger_name, deletion_protection): 69 | """ 70 | Update an existing ledger's deletion protection. 71 | 72 | :type ledger_name: str 73 | :param ledger_name: Name of the ledger to update. 74 | 75 | :type deletion_protection: bool 76 | :param deletion_protection: Enable or disable the deletion protection. 77 | 78 | :rtype: dict 79 | :return: Result from the request. 80 | """ 81 | logger.info("Let's set deletion protection to {} for the ledger with name {}.".format(deletion_protection, 82 | ledger_name)) 83 | result = qldb_client.update_ledger(Name=ledger_name, DeletionProtection=deletion_protection) 84 | logger.info('Success. Ledger updated: {}'.format(result)) 85 | 86 | 87 | def main(ledger_name=Constants.LEDGER_NAME): 88 | """ 89 | Delete a ledger. 90 | """ 91 | try: 92 | set_deletion_protection(ledger_name, False) 93 | delete_ledger(ledger_name) 94 | wait_for_deleted(ledger_name) 95 | except Exception as e: 96 | logger.exception('Unable to delete the ledger.') 97 | raise e 98 | 99 | 100 | if __name__ == '__main__': 101 | main() 102 | -------------------------------------------------------------------------------- /pyqldbsamples/deletion_protection.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | from pyqldbsamples.create_ledger import wait_for_active 24 | from pyqldbsamples.delete_ledger import delete_ledger, set_deletion_protection 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | qldb_client = client('qldb') 29 | 30 | LEDGER_NAME = 'deletion-protection-demo' 31 | 32 | 33 | def create_with_deletion_protection(ledger_name): 34 | """ 35 | Create a new ledger with the specified name and with deletion protection enabled. 36 | 37 | :type ledger_name: str 38 | :param ledger_name: Name for the ledger to be created. 39 | 40 | :rtype: dict 41 | :return: Result from the request. 42 | """ 43 | logger.info("Let's create the ledger with name: {}...".format(ledger_name)) 44 | result = qldb_client.create_ledger(Name=ledger_name, PermissionsMode='ALLOW_ALL') 45 | logger.info('Success. Ledger state: {}'.format(result.get('State'))) 46 | return result 47 | 48 | 49 | def main(ledger_name=LEDGER_NAME): 50 | """ 51 | Demonstrate the protection of QLDB ledgers against deletion. 52 | """ 53 | try: 54 | create_with_deletion_protection(ledger_name) 55 | wait_for_active(ledger_name) 56 | try: 57 | delete_ledger(ledger_name) 58 | except qldb_client.exceptions.ResourcePreconditionNotMetException: 59 | logger.info('Ledger protected against deletions! Turning off deletion protection now.') 60 | set_deletion_protection(ledger_name, False) 61 | delete_ledger(ledger_name) 62 | except Exception as e: 63 | logger.exception('Error while updating or deleting the ledger!') 64 | raise e 65 | 66 | 67 | if __name__ == '__main__': 68 | main() 69 | -------------------------------------------------------------------------------- /pyqldbsamples/deregister_drivers_license.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 22 | from pyqldbsamples.constants import Constants 23 | from pyqldbsamples.model.sample_data import SampleData, convert_object_to_ion 24 | 25 | logger = getLogger(__name__) 26 | basicConfig(level=INFO) 27 | 28 | 29 | def deregister_drivers_license(driver, license_number): 30 | """ 31 | De-register a driver's license with the given license number. 32 | 33 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 34 | :param driver: An instance of the QldbDriver class. 35 | 36 | :type license_number: str 37 | :param license_number: The license number of the driver's license to de-register. 38 | """ 39 | logger.info('De-registering license with license number: {}.'.format(license_number)) 40 | statement = 'DELETE FROM DriversLicense AS d WHERE d.LicenseNumber = ?' 41 | parameter = convert_object_to_ion(license_number) 42 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement, parameter)) 43 | 44 | try: 45 | # Check whether cursor is empty. 46 | next(cursor) 47 | logger.info("Successfully de-registered driver's license: {}.".format(license_number)) 48 | except StopIteration: 49 | logger.error('Error de-registering license, license {} not found.'.format(license_number)) 50 | 51 | 52 | def main(ledger_name=Constants.LEDGER_NAME): 53 | """ 54 | De-register a driver's license. 55 | """ 56 | license_number = SampleData.DRIVERS_LICENSE[1]['LicenseNumber'] 57 | 58 | try: 59 | with create_qldb_driver(ledger_name) as driver: 60 | deregister_drivers_license(driver, license_number) 61 | except Exception as e: 62 | logger.exception('Error deleting driver license.') 63 | raise e 64 | 65 | 66 | if __name__ == '__main__': 67 | main() 68 | -------------------------------------------------------------------------------- /pyqldbsamples/describe_journal_export.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | from sys import argv 21 | 22 | from boto3 import client 23 | 24 | from pyqldbsamples.constants import Constants 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | qldb_client = client('qldb') 29 | 30 | 31 | def describe_journal_export(ledger_name, export_id): 32 | """ 33 | Describe a journal export. 34 | 35 | :type ledger_name: str 36 | :param ledger_name: The ledger from which the journal is being exported. 37 | 38 | :type export_id: str 39 | :param export_id: The ExportId of the journal. 40 | 41 | :rtype: dict 42 | :return: Result from the request. 43 | """ 44 | logger.info("Let's describe a journal export for ledger with name: {}, exportId: {}.".format(ledger_name, 45 | export_id)) 46 | export_result = qldb_client.describe_journal_s3_export(Name=ledger_name, ExportId=export_id) 47 | logger.info('Export described. Result = {}.'.format(export_result['ExportDescription'])) 48 | return export_result 49 | 50 | 51 | def main(ledger_name=Constants.LEDGER_NAME): 52 | """ 53 | Describe a specific journal export with the given ExportId. 54 | """ 55 | if len(argv) != 2: 56 | raise ValueError('Missing ExportId argument in DescribeJournalExport') 57 | export_id = argv[1] 58 | 59 | logger.info('Running describe export journal tutorial with ExportId: {}.'.format(export_id)) 60 | 61 | try: 62 | describe_journal_export(ledger_name, export_id) 63 | except Exception as e: 64 | logger.exception('Unable to describe an export!') 65 | raise e 66 | 67 | 68 | if __name__ == '__main__': 69 | main() 70 | -------------------------------------------------------------------------------- /pyqldbsamples/describe_ledger.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | from pyqldbsamples.constants import Constants 24 | 25 | logger = getLogger(__name__) 26 | basicConfig(level=INFO) 27 | qldb_client = client('qldb') 28 | 29 | 30 | def describe_ledger(ledger_name): 31 | """ 32 | Describe a ledger. 33 | 34 | :type ledger_name: str 35 | :param ledger_name: Name of the ledger to describe. 36 | """ 37 | logger.info('describe ledger with name: {}.'.format(ledger_name)) 38 | result = qldb_client.describe_ledger(Name=ledger_name) 39 | result.pop('ResponseMetadata') 40 | logger.info('Success. Ledger description: {}.'.format(result)) 41 | return result 42 | 43 | 44 | def main(ledger_name=Constants.LEDGER_NAME): 45 | """ 46 | Describe a QLDB ledger. 47 | """ 48 | try: 49 | describe_ledger(ledger_name) 50 | except Exception as e: 51 | logger.exception('Unable to describe a ledger.') 52 | raise e 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /pyqldbsamples/find_vehicles.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.model.sample_data import get_document_ids, print_result, SampleData 22 | from pyqldbsamples.constants import Constants 23 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 24 | 25 | logger = getLogger(__name__) 26 | basicConfig(level=INFO) 27 | 28 | 29 | def find_vehicles_for_owner(driver, gov_id): 30 | """ 31 | Find vehicles registered under a driver using their government ID. 32 | 33 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 34 | :param driver: An instance of the QldbDriver class. 35 | 36 | :type gov_id: str 37 | :param gov_id: The owner's government ID. 38 | """ 39 | document_ids = driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME, 40 | 'GovId', gov_id)) 41 | 42 | query = "SELECT Vehicle FROM Vehicle INNER JOIN VehicleRegistration AS r " \ 43 | "ON Vehicle.VIN = r.VIN WHERE r.Owners.PrimaryOwner.PersonId = ?" 44 | 45 | for ids in document_ids: 46 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, ids)) 47 | logger.info('List of Vehicles for owner with GovId: {}...'.format(gov_id)) 48 | print_result(cursor) 49 | 50 | 51 | def main(ledger_name=Constants.LEDGER_NAME): 52 | """ 53 | Find all vehicles registered under a person. 54 | """ 55 | try: 56 | with create_qldb_driver(ledger_name) as driver: 57 | # Find all vehicles registered under a person. 58 | gov_id = SampleData.PERSON[0]['GovId'] 59 | find_vehicles_for_owner(driver, gov_id) 60 | except Exception as e: 61 | logger.exception('Error getting vehicles for owner.') 62 | raise e 63 | 64 | 65 | if __name__ == '__main__': 66 | main() 67 | -------------------------------------------------------------------------------- /pyqldbsamples/get_block.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | from pyqldbsamples.constants import Constants 24 | from pyqldbsamples.model.sample_data import SampleData 25 | from pyqldbsamples.get_revision import lookup_registration_for_vin 26 | from pyqldbsamples.get_digest import get_digest_result 27 | from pyqldbsamples.verifier import to_base_64, verify_document, parse_block, flip_random_bit 28 | from pyqldbsamples.qldb.block_address import block_address_to_dictionary 29 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 30 | from pyqldbsamples.qldb.qldb_string_utils import block_response_to_string, value_holder_to_string 31 | 32 | logger = getLogger(__name__) 33 | basicConfig(level=INFO) 34 | qldb_client = client('qldb') 35 | 36 | 37 | def get_block(ledger_name, block_address): 38 | """ 39 | Get the block of a ledger's journal. 40 | 41 | :type ledger_name: str 42 | :param ledger_name: Name of the ledger to operate on. 43 | 44 | :type block_address: dict 45 | :param block_address: The location of the block to request. 46 | 47 | :rtype: dict 48 | :return: The response of the request. 49 | """ 50 | logger.info("Let's get the block for block address {} of the ledger named {}.".format(block_address, ledger_name)) 51 | result = qldb_client.get_block(Name=ledger_name, BlockAddress=block_address) 52 | logger.info('Success. GetBlock: {}'.format(block_response_to_string(result))) 53 | return result 54 | 55 | 56 | def get_block_with_proof(ledger_name, block_address, digest_tip_address): 57 | """ 58 | Get the block of a ledger's journal. Also returns a proof of the block for verification. 59 | 60 | :type ledger_name: str 61 | :param ledger_name: Name of the ledger to operate on. 62 | 63 | :type block_address: dict 64 | :param block_address: The location of the block to request. 65 | 66 | :type digest_tip_address: dict 67 | :param digest_tip_address: The location of the digest tip. 68 | 69 | :rtype: dict 70 | :return: The response of the request. 71 | """ 72 | logger.info("Let's get the block for block address {}, digest tip address {}, for the ledger named {}.".format( 73 | block_address, digest_tip_address, ledger_name)) 74 | result = qldb_client.get_block(Name=ledger_name, BlockAddress=block_address, 75 | DigestTipAddress=digest_tip_address) 76 | logger.info('Success. GetBlock: {}.'.format(block_response_to_string(result))) 77 | return result 78 | 79 | 80 | def verify_block(ledger_name, block_address): 81 | """ 82 | Verify block by validating the proof returned in the getBlock response. 83 | 84 | :type ledger_name: str 85 | :param ledger_name: The ledger to get digest from. 86 | 87 | :type block_address: str/:py:class:`amazon.ion.simple_types.IonPyDict` 88 | :param block_address: The address of the block to verify. 89 | 90 | :raises AssertionError: When verification failed. 91 | """ 92 | logger.info("Let's verify blocks for ledger with name={}.".format(ledger_name)) 93 | 94 | try: 95 | logger.info("First, let's get a digest.") 96 | digest_result = get_digest_result(ledger_name) 97 | 98 | digest_tip_address = digest_result.get('DigestTipAddress') 99 | digest_bytes = digest_result.get('Digest') 100 | 101 | logger.info('Got a ledger digest. Digest end address={}, digest={}'.format( 102 | value_holder_to_string(digest_tip_address.get('IonText')), to_base_64(digest_bytes))) 103 | get_block_result = get_block_with_proof(ledger_name, block_address_to_dictionary(block_address), 104 | digest_tip_address) 105 | block = get_block_result.get('Block') 106 | block_hash = parse_block(block) 107 | 108 | verified = verify_document(block_hash, digest_bytes, get_block_result.get('Proof')) 109 | 110 | if not verified: 111 | raise AssertionError('Block is not verified!') 112 | else: 113 | logger.info('Success! The block is verified.') 114 | 115 | altered_digest = flip_random_bit(digest_bytes) 116 | logger.info("Let's try flipping one bit in the digest and assert that the block is NOT verified. " 117 | "The altered digest is: {}".format(to_base_64(altered_digest))) 118 | 119 | verified = verify_document(block_hash, altered_digest, get_block_result.get('Proof')) 120 | 121 | if verified: 122 | raise AssertionError('Expected block to not be verified against altered digest.') 123 | else: 124 | logger.info('Success! As expected flipping a bit in the digest causes verification to fail.') 125 | 126 | altered_block_hash = flip_random_bit(block_hash) 127 | logger.info("Let's try flipping one bit in the block's hash and assert that the block is NOT verified. " 128 | "The altered block hash is: {}.".format(to_base_64(altered_block_hash))) 129 | 130 | verified = verify_document(altered_block_hash, digest_bytes, get_block_result.get('Proof')) 131 | 132 | if verified: 133 | raise AssertionError('Expected altered block hash to not be verified against digest.') 134 | else: 135 | logger.info('Success! As expected flipping a bit in the block hash causes verification to fail.') 136 | except Exception as e: 137 | logger.exception('Failed to verify blocks in the ledger with name={}.'.format(ledger_name)) 138 | raise e 139 | 140 | 141 | def main(ledger_name=Constants.LEDGER_NAME): 142 | """ 143 | Get a journal block from a QLDB ledger. 144 | 145 | After getting the block, we get the digest of the ledger and validate the 146 | proof returned in the getBlock response. 147 | """ 148 | vin = SampleData.VEHICLE_REGISTRATION[1]['VIN'] 149 | try: 150 | with create_qldb_driver(ledger_name) as driver: 151 | cursor = lookup_registration_for_vin(driver, vin) 152 | row = next(cursor) 153 | block_address = row.get('blockAddress') 154 | verify_block(ledger_name, block_address) 155 | except Exception as e: 156 | logger.exception('Unable to query vehicle registration by Vin.') 157 | raise e 158 | 159 | 160 | if __name__ == '__main__': 161 | main() 162 | -------------------------------------------------------------------------------- /pyqldbsamples/get_digest.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | from pyqldbsamples.constants import Constants 24 | from pyqldbsamples.qldb.qldb_string_utils import digest_response_to_string 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | qldb_client = client('qldb') 29 | 30 | 31 | def get_digest_result(name): 32 | """ 33 | Get the digest of a ledger's journal. 34 | 35 | :type name: str 36 | :param name: Name of the ledger to operate on. 37 | 38 | :rtype: dict 39 | :return: The digest in a 256-bit hash value and a block address. 40 | """ 41 | logger.info("Let's get the current digest of the ledger named {}".format(name)) 42 | result = qldb_client.get_digest(Name=name) 43 | logger.info('Success. LedgerDigest: {}.'.format(digest_response_to_string(result))) 44 | return result 45 | 46 | 47 | def main(ledger_name=Constants.LEDGER_NAME): 48 | """ 49 | This is an example for retrieving the digest of a particular ledger. 50 | """ 51 | try: 52 | get_digest_result(ledger_name) 53 | except Exception as e: 54 | logger.exception('Unable to get a ledger digest!') 55 | raise e 56 | 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /pyqldbsamples/get_revision.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from amazon.ion.simpleion import loads 22 | from boto3 import client 23 | 24 | from pyqldbsamples.constants import Constants 25 | from pyqldbsamples.get_digest import get_digest_result 26 | from pyqldbsamples.model.sample_data import SampleData, convert_object_to_ion 27 | from pyqldbsamples.qldb.block_address import block_address_to_dictionary 28 | from pyqldbsamples.verifier import verify_document, flip_random_bit, to_base_64 29 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 30 | from pyqldbsamples.qldb.qldb_string_utils import value_holder_to_string 31 | 32 | logger = getLogger(__name__) 33 | basicConfig(level=INFO) 34 | qldb_client = client('qldb') 35 | 36 | 37 | def get_revision(ledger_name, document_id, block_address, digest_tip_address): 38 | """ 39 | Get the revision data object for a specified document ID and block address. 40 | Also returns a proof of the specified revision for verification. 41 | 42 | :type ledger_name: str 43 | :param ledger_name: Name of the ledger containing the document to query. 44 | 45 | :type document_id: str 46 | :param document_id: Unique ID for the document to be verified, contained in the committed view of the document. 47 | 48 | :type block_address: dict 49 | :param block_address: The location of the block to request. 50 | 51 | :type digest_tip_address: dict 52 | :param digest_tip_address: The latest block location covered by the digest. 53 | 54 | :rtype: dict 55 | :return: The response of the request. 56 | """ 57 | result = qldb_client.get_revision(Name=ledger_name, BlockAddress=block_address, DocumentId=document_id, 58 | DigestTipAddress=digest_tip_address) 59 | return result 60 | 61 | 62 | def lookup_registration_for_vin(driver, vin): 63 | """ 64 | Query revision history for a particular vehicle for verification. 65 | 66 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 67 | :param driver: An instance of the QldbDriver class. 68 | 69 | :type vin: str 70 | :param vin: VIN to query the revision history of a specific registration with. 71 | 72 | :rtype: :py:class:`pyqldb.cursor.buffered_cursor.BufferedCursor` 73 | :return: Cursor on the result set of the statement query. 74 | """ 75 | logger.info("Querying the 'VehicleRegistration' table for VIN: {}...".format(vin)) 76 | query = 'SELECT * FROM _ql_committed_VehicleRegistration WHERE data.VIN = ?' 77 | return driver.execute_lambda(lambda txn: txn.execute_statement(query, convert_object_to_ion(vin))) 78 | 79 | 80 | def verify_registration(driver, ledger_name, vin): 81 | """ 82 | Verify each version of the registration for the given VIN. 83 | 84 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 85 | :param driver: An instance of the QldbDriver class. 86 | 87 | :type ledger_name: str 88 | :param ledger_name: The ledger to get digest from. 89 | 90 | :type vin: str 91 | :param vin: VIN to query the revision history of a specific registration with. 92 | 93 | :raises AssertionError: When verification failed. 94 | """ 95 | logger.info("Let's verify the registration with VIN = {}, in ledger = {}.".format(vin, ledger_name)) 96 | digest = get_digest_result(ledger_name) 97 | digest_bytes = digest.get('Digest') 98 | digest_tip_address = digest.get('DigestTipAddress') 99 | 100 | logger.info('Got a ledger digest: digest tip address = {}, digest = {}.'.format( 101 | value_holder_to_string(digest_tip_address.get('IonText')), to_base_64(digest_bytes))) 102 | 103 | logger.info('Querying the registration with VIN = {} to verify each version of the registration...'.format(vin)) 104 | cursor = lookup_registration_for_vin(driver, vin) 105 | logger.info('Getting a proof for the document.') 106 | 107 | for row in cursor: 108 | block_address = row.get('blockAddress') 109 | document_id = row.get('metadata').get('id') 110 | 111 | result = get_revision(ledger_name, document_id, block_address_to_dictionary(block_address), digest_tip_address) 112 | revision = result.get('Revision').get('IonText') 113 | document_hash = loads(revision).get('hash') 114 | 115 | proof = result.get('Proof') 116 | logger.info('Got back a proof: {}.'.format(proof)) 117 | 118 | verified = verify_document(document_hash, digest_bytes, proof) 119 | if not verified: 120 | raise AssertionError('Document revision is not verified.') 121 | else: 122 | logger.info('Success! The document is verified.') 123 | 124 | altered_document_hash = flip_random_bit(document_hash) 125 | logger.info("Flipping one bit in the document's hash and assert that the document is NOT verified. " 126 | "The altered document hash is: {}.".format(to_base_64(altered_document_hash))) 127 | verified = verify_document(altered_document_hash, digest_bytes, proof) 128 | if verified: 129 | raise AssertionError('Expected altered document hash to not be verified against digest.') 130 | else: 131 | logger.info('Success! As expected flipping a bit in the document hash causes verification to fail.') 132 | 133 | logger.info('Finished verifying the registration with VIN = {} in ledger = {}.'.format(vin, ledger_name)) 134 | 135 | 136 | def main(ledger_name=Constants.LEDGER_NAME): 137 | """ 138 | Verify the integrity of a document revision in a QLDB ledger. 139 | """ 140 | registration = SampleData.VEHICLE_REGISTRATION[0] 141 | vin = registration['VIN'] 142 | try: 143 | with create_qldb_driver(ledger_name) as driver: 144 | verify_registration(driver, ledger_name, vin) 145 | except Exception as e: 146 | logger.exception('Unable to verify revision.') 147 | raise e 148 | 149 | 150 | if __name__ == '__main__': 151 | main() 152 | -------------------------------------------------------------------------------- /pyqldbsamples/insert_document.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.constants import Constants 22 | from pyqldbsamples.model.sample_data import convert_object_to_ion, SampleData, get_document_ids_from_dml_results 23 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 24 | 25 | logger = getLogger(__name__) 26 | basicConfig(level=INFO) 27 | 28 | 29 | def update_person_id(document_ids): 30 | """ 31 | Update the PersonId value for DriversLicense records and the PrimaryOwner value for VehicleRegistration records. 32 | 33 | :type document_ids: list 34 | :param document_ids: List of document IDs. 35 | 36 | :rtype: list 37 | :return: Lists of updated DriversLicense records and updated VehicleRegistration records. 38 | """ 39 | new_drivers_licenses = SampleData.DRIVERS_LICENSE.copy() 40 | new_vehicle_registrations = SampleData.VEHICLE_REGISTRATION.copy() 41 | for i in range(len(SampleData.PERSON)): 42 | drivers_license = new_drivers_licenses[i] 43 | registration = new_vehicle_registrations[i] 44 | drivers_license.update({'PersonId': str(document_ids[i])}) 45 | registration['Owners']['PrimaryOwner'].update({'PersonId': str(document_ids[i])}) 46 | return new_drivers_licenses, new_vehicle_registrations 47 | 48 | 49 | def insert_documents(driver, table_name, documents): 50 | """ 51 | Insert the given list of documents into a table in a single transaction. 52 | 53 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 54 | :param driver: An instance of the QldbDriver class. 55 | 56 | :type table_name: str 57 | :param table_name: Name of the table to insert documents into. 58 | 59 | :type documents: list 60 | :param documents: List of documents to insert. 61 | 62 | :rtype: list 63 | :return: List of documents IDs for the newly inserted documents. 64 | """ 65 | logger.info('Inserting some documents in the {} table...'.format(table_name)) 66 | statement = 'INSERT INTO {} ?'.format(table_name) 67 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement, 68 | convert_object_to_ion(documents))) 69 | list_of_document_ids = get_document_ids_from_dml_results(cursor) 70 | 71 | return list_of_document_ids 72 | 73 | 74 | def update_and_insert_documents(driver): 75 | """ 76 | Handle the insertion of documents and updating PersonIds. 77 | 78 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 79 | :param driver: An instance of the QldbDriver class. 80 | """ 81 | list_ids = insert_documents(driver, Constants.PERSON_TABLE_NAME, SampleData.PERSON) 82 | 83 | logger.info("Updating PersonIds for 'DriversLicense' and PrimaryOwner for 'VehicleRegistration'...") 84 | new_licenses, new_registrations = update_person_id(list_ids) 85 | 86 | insert_documents(driver, Constants.VEHICLE_TABLE_NAME, SampleData.VEHICLE) 87 | insert_documents(driver, Constants.VEHICLE_REGISTRATION_TABLE_NAME, new_registrations) 88 | insert_documents(driver, Constants.DRIVERS_LICENSE_TABLE_NAME, new_licenses) 89 | 90 | 91 | def main(ledger_name=Constants.LEDGER_NAME): 92 | """ 93 | Insert documents into a table in a QLDB ledger. 94 | """ 95 | try: 96 | with create_qldb_driver(ledger_name) as driver: 97 | # An INSERT statement creates the initial revision of a document with a version number of zero. 98 | # QLDB also assigns a unique document identifier in GUID format as part of the metadata. 99 | update_and_insert_documents(driver) 100 | logger.info('Documents inserted successfully!') 101 | except Exception as e: 102 | logger.exception('Error inserting or updating documents.') 103 | raise e 104 | 105 | 106 | if __name__ == '__main__': 107 | main() 108 | -------------------------------------------------------------------------------- /pyqldbsamples/insert_ion_types.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from datetime import datetime 20 | from decimal import Decimal 21 | from logging import basicConfig, getLogger, INFO 22 | 23 | from amazon.ion.simple_types import IonPyBool, IonPyBytes, IonPyDecimal, IonPyDict, IonPyFloat, IonPyInt, IonPyList, \ 24 | IonPyNull, IonPySymbol, IonPyText, IonPyTimestamp 25 | from amazon.ion.simpleion import loads 26 | from amazon.ion.symbols import SymbolToken 27 | from amazon.ion.core import IonType 28 | 29 | from pyqldbsamples.create_table import create_table 30 | from pyqldbsamples.constants import Constants 31 | from pyqldbsamples.insert_document import insert_documents 32 | from pyqldbsamples.model.sample_data import convert_object_to_ion 33 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 34 | 35 | logger = getLogger(__name__) 36 | basicConfig(level=INFO) 37 | 38 | TABLE_NAME = 'IonTypes' 39 | 40 | 41 | def update_record_and_verify_type(driver, parameter, ion_object, ion_type): 42 | """ 43 | Update a record in the database table. Then query the value of the record and verify correct ion type saved. 44 | 45 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 46 | :param driver: An instance of the QldbDriver class. 47 | 48 | :type parameter: :py:class:`amazon.ion.simple_types.IonPyValue` 49 | :param parameter: The Ion value or Python native type that is convertible to Ion for filling in parameters of the 50 | statement. 51 | 52 | :type ion_object: :py:obj:`IonPyBool`/:py:obj:`IonPyBytes`/:py:obj:`IonPyDecimal`/:py:obj:`IonPyDict` 53 | /:py:obj:`IonPyFloat`/:py:obj:`IonPyInt`/:py:obj:`IonPyList`/:py:obj:`IonPyNull` 54 | /:py:obj:`IonPySymbol`/:py:obj:`IonPyText`/:py:obj:`IonPyTimestamp` 55 | :param ion_object: The Ion object to verify against. 56 | 57 | :type ion_type: :py:class:`amazon.ion.core.IonType` 58 | :param ion_type: The Ion type to verify against. 59 | 60 | :raises TypeError: When queried value is not an instance of Ion type. 61 | """ 62 | update_query = 'UPDATE {} SET Name = ?'.format(TABLE_NAME) 63 | driver.execute_lambda(lambda executor: executor.execute_statement(update_query, parameter)) 64 | logger.info('Updated record.') 65 | 66 | search_query = 'SELECT VALUE Name FROM {}'.format(TABLE_NAME) 67 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(search_query)) 68 | 69 | for c in cursor: 70 | if not isinstance(c, ion_object): 71 | raise TypeError('The queried value is not an instance of {}'.format(ion_object.__name__)) 72 | 73 | if c.ion_type is not ion_type: 74 | raise TypeError('The queried value type does not match {}'.format(ion_type)) 75 | 76 | logger.info("Successfully verified value is instance of '{}' with type '{}'.".format(ion_object.__name__, ion_type)) 77 | return cursor 78 | 79 | 80 | def delete_table(driver, table_name): 81 | """ 82 | Delete a table. 83 | 84 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 85 | :param driver: An instance of the QldbDriver class. 86 | 87 | :type table_name: str 88 | :param table_name: Name of the table to delete. 89 | 90 | :rtype: int 91 | :return: The number of changes to the database. 92 | """ 93 | logger.info("Deleting '{}' table...".format(table_name)) 94 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement('DROP TABLE {}'.format(table_name))) 95 | logger.info("'{}' table successfully deleted.".format(table_name)) 96 | return len(list(cursor)) 97 | 98 | 99 | def insert_and_verify_ion_types(driver): 100 | """ 101 | Insert all the supported Ion types and Python values that are convertible to Ion into a ledger and verify that they 102 | are stored and can be retrieved properly, retaining their original properties. 103 | 104 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 105 | :param driver: A QLDB Driver object. 106 | """ 107 | python_bytes = str.encode('hello') 108 | python_bool = True 109 | python_float = float('0.2') 110 | python_decimal = Decimal('0.1') 111 | python_string = "string" 112 | python_int = 1 113 | python_null = None 114 | python_datetime = datetime(2016, 12, 20, 5, 23, 43) 115 | python_list = [1, 2] 116 | python_dict = {"brand": "Ford"} 117 | 118 | ion_clob = convert_object_to_ion(loads('{{"This is a CLOB of text."}}')) 119 | ion_blob = convert_object_to_ion(python_bytes) 120 | ion_bool = convert_object_to_ion(python_bool) 121 | ion_decimal = convert_object_to_ion(python_decimal) 122 | ion_float = convert_object_to_ion(python_float) 123 | ion_int = convert_object_to_ion(python_int) 124 | ion_list = convert_object_to_ion(python_list) 125 | ion_null = convert_object_to_ion(python_null) 126 | ion_sexp = convert_object_to_ion(loads('(cons 1 2)')) 127 | ion_string = convert_object_to_ion(python_string) 128 | ion_struct = convert_object_to_ion(python_dict) 129 | ion_symbol = convert_object_to_ion(SymbolToken(text='abc', sid=123)) 130 | ion_timestamp = convert_object_to_ion(python_datetime) 131 | 132 | ion_null_clob = convert_object_to_ion(loads('null.clob')) 133 | ion_null_blob = convert_object_to_ion(loads('null.blob')) 134 | ion_null_bool = convert_object_to_ion(loads('null.bool')) 135 | ion_null_decimal = convert_object_to_ion(loads('null.decimal')) 136 | ion_null_float = convert_object_to_ion(loads('null.float')) 137 | ion_null_int = convert_object_to_ion(loads('null.int')) 138 | ion_null_list = convert_object_to_ion(loads('null.list')) 139 | ion_null_sexp = convert_object_to_ion(loads('null.sexp')) 140 | ion_null_string = convert_object_to_ion(loads('null.string')) 141 | ion_null_struct = convert_object_to_ion(loads('null.struct')) 142 | ion_null_symbol = convert_object_to_ion(loads('null.symbol')) 143 | ion_null_timestamp = convert_object_to_ion(loads('null.timestamp')) 144 | 145 | create_table(driver, TABLE_NAME) 146 | insert_documents(driver, TABLE_NAME, [{'Name': 'val'}]) 147 | update_record_and_verify_type(driver, python_bytes, IonPyBytes, IonType.BLOB) 148 | update_record_and_verify_type(driver, python_bool, IonPyBool, IonType.BOOL) 149 | update_record_and_verify_type(driver, python_float, IonPyFloat, IonType.FLOAT) 150 | update_record_and_verify_type(driver, python_decimal, IonPyDecimal, IonType.DECIMAL) 151 | update_record_and_verify_type(driver, python_string, IonPyText, IonType.STRING) 152 | update_record_and_verify_type(driver, python_int, IonPyInt, IonType.INT) 153 | update_record_and_verify_type(driver, python_null, IonPyNull, IonType.NULL) 154 | update_record_and_verify_type(driver, python_datetime, IonPyTimestamp, IonType.TIMESTAMP) 155 | update_record_and_verify_type(driver, python_list, IonPyList, IonType.LIST) 156 | update_record_and_verify_type(driver, python_dict, IonPyDict, IonType.STRUCT) 157 | update_record_and_verify_type(driver, ion_clob, IonPyBytes, IonType.CLOB) 158 | update_record_and_verify_type(driver, ion_blob, IonPyBytes, IonType.BLOB) 159 | update_record_and_verify_type(driver, ion_bool, IonPyBool, IonType.BOOL) 160 | update_record_and_verify_type(driver, ion_decimal, IonPyDecimal, IonType.DECIMAL) 161 | update_record_and_verify_type(driver, ion_float, IonPyFloat, IonType.FLOAT) 162 | update_record_and_verify_type(driver, ion_int, IonPyInt, IonType.INT) 163 | update_record_and_verify_type(driver, ion_list, IonPyList, IonType.LIST) 164 | update_record_and_verify_type(driver, ion_null, IonPyNull, IonType.NULL) 165 | update_record_and_verify_type(driver, ion_sexp, IonPyList, IonType.SEXP) 166 | update_record_and_verify_type(driver, ion_string, IonPyText, IonType.STRING) 167 | update_record_and_verify_type(driver, ion_struct, IonPyDict, IonType.STRUCT) 168 | update_record_and_verify_type(driver, ion_symbol, IonPySymbol, IonType.SYMBOL) 169 | update_record_and_verify_type(driver, ion_timestamp, IonPyTimestamp, IonType.TIMESTAMP) 170 | update_record_and_verify_type(driver, ion_null_clob, IonPyNull, IonType.CLOB) 171 | update_record_and_verify_type(driver, ion_null_blob, IonPyNull, IonType.BLOB) 172 | update_record_and_verify_type(driver, ion_null_bool, IonPyNull, IonType.BOOL) 173 | update_record_and_verify_type(driver, ion_null_decimal, IonPyNull, IonType.DECIMAL) 174 | update_record_and_verify_type(driver, ion_null_float, IonPyNull, IonType.FLOAT) 175 | update_record_and_verify_type(driver, ion_null_int, IonPyNull, IonType.INT) 176 | update_record_and_verify_type(driver, ion_null_list, IonPyNull, IonType.LIST) 177 | update_record_and_verify_type(driver, ion_null_sexp, IonPyNull, IonType.SEXP) 178 | update_record_and_verify_type(driver, ion_null_string, IonPyNull, IonType.STRING) 179 | update_record_and_verify_type(driver, ion_null_struct, IonPyNull, IonType.STRUCT) 180 | update_record_and_verify_type(driver, ion_null_symbol, IonPyNull, IonType.SYMBOL) 181 | update_record_and_verify_type(driver, ion_null_timestamp, IonPyNull, IonType.TIMESTAMP) 182 | delete_table(driver, TABLE_NAME) 183 | 184 | 185 | def main(ledger_name=Constants.LEDGER_NAME): 186 | """ 187 | Insert all the supported Ion types and Python values that are convertible to Ion into a ledger and verify that they 188 | are stored and can be retrieved properly, retaining their original properties. 189 | """ 190 | try: 191 | with create_qldb_driver(ledger_name) as driver: 192 | insert_and_verify_ion_types(driver) 193 | except Exception as e: 194 | logger.exception('Error updating and validating Ion types.') 195 | raise e 196 | 197 | 198 | if __name__ == '__main__': 199 | main() 200 | -------------------------------------------------------------------------------- /pyqldbsamples/journal_s3_export_reader.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from json import dumps as json_dumps 20 | from logging import getLogger 21 | 22 | from amazon.ion.simpleion import loads 23 | 24 | from pyqldbsamples.qldb.journal_block import from_ion 25 | 26 | logger = getLogger(__name__) 27 | 28 | 29 | def compare_key_with_content_range(file_key, first_block, last_block): 30 | """ 31 | Compare the expected block range, derived from File Key, with the actual object content. 32 | 33 | :type file_key: str 34 | :param file_key: The key of data file containing the chunk of journal block. The fileKey pattern is 35 | {[strandId].[firstSequenceNo]-[lastSequenceNo].ion}. 36 | 37 | :type first_block: :py:class:`pyqldbsamples.qldb.journal_block.JournalBlock` 38 | :param first_block: The first block in the block chain for a particular journal strand. 39 | 40 | :type last_block: :py:class:`pyqldbsamples.qldb.journal_block.JournalBlock` 41 | :param last_block: The last block in the block chain for a particular journal strand. 42 | 43 | :raises RuntimeError: If the SequenceNo on the blockAddress does not match the expected SequenceNo. 44 | """ 45 | sequence_no_range = file_key.split(".")[1] 46 | key_tokens = sequence_no_range.split("-") 47 | start_sequence_no = key_tokens[0] 48 | last_sequence_no = key_tokens[1] 49 | 50 | if str(first_block.block_address.get("sequenceNo")) != start_sequence_no: 51 | raise RuntimeError('Expected first block SequenceNo to be {}.'.format(start_sequence_no)) 52 | if str(last_block.block_address.get("sequenceNo")) != last_sequence_no: 53 | raise RuntimeError('Expected last block SequenceNo to be {}.'.format(last_sequence_no)) 54 | 55 | 56 | def filter_for_initial_manifest(objects, manifest): 57 | """ 58 | Find the initial manifest created at the beginning of a export request. 59 | 60 | :type objects: list 61 | :param objects: List of objects in a particular bucket. 62 | 63 | :type manifest: str 64 | :param manifest: The expected identifier for the initial manifest. 65 | 66 | :rtype: str 67 | :return: The identifier for the initial manifest object. 68 | 69 | :raises RuntimeError: If the initial manifest is not found. 70 | """ 71 | for obj in objects: 72 | key = obj['Key'].casefold() 73 | if key == manifest.casefold(): 74 | return key 75 | raise RuntimeError('Initial manifest not found.') 76 | 77 | 78 | def filter_for_completed_manifest(objects): 79 | """ 80 | Find the final manifest objects created after the completion of an export job. 81 | 82 | :type objects: list 83 | :param objects: List of objects in a particular bucket. 84 | 85 | :rtype: str 86 | :return: The identifier for the final manifest object. 87 | 88 | :raises RuntimeError: If the final manifest is not found. 89 | """ 90 | for obj in objects: 91 | key = obj['Key'] 92 | if key.casefold().endswith("completed.manifest"): 93 | return key 94 | raise RuntimeError('Completed manifest not found.') 95 | 96 | 97 | def get_data_file_keys_from_manifest(manifest_object): 98 | """ 99 | Retrieve the ordered list of data object keys within the given final manifest. 100 | 101 | :type manifest_object: str 102 | :param manifest_object: The content of the final manifest. 103 | 104 | :rtype: list 105 | :return: List of data object keys. 106 | """ 107 | ion_keys = loads(manifest_object).get('keys') 108 | list_of_keys = list(ion_keys) 109 | return list_of_keys 110 | 111 | 112 | def get_journal_blocks(s3_object): 113 | """ 114 | Parse the given S3 object's content for the journal data objects in Ion format. 115 | 116 | :type s3_object: str 117 | :param s3_object: The content within a S3 object as an ion string. 118 | 119 | :rtype: list 120 | :return: List of journal blocks. 121 | 122 | :raises RuntimeError: If there is an error loading the journal. 123 | """ 124 | journals = loads(s3_object, single_value=False) 125 | journal_blocks = [] 126 | 127 | try: 128 | for journal in journals: 129 | parsed_journal = from_ion(journal) 130 | journal_blocks.append(parsed_journal) 131 | except Exception: 132 | logger.log("Invalid format to map to a JournalBlock!") 133 | raise Exception 134 | 135 | logger.info('Found {} block(s).'.format(len(journal_blocks))) 136 | return journal_blocks 137 | 138 | 139 | def read_export(describe_journal_export_result, s3_client): 140 | """ 141 | Read the S3 export within a journal block. 142 | 143 | :type describe_journal_export_result: dict 144 | :param describe_journal_export_result: The result from QLDB describing a journal export. 145 | 146 | :type s3_client: :py:class:`botocore.client.BaseClient` 147 | :param s3_client: The low-level S3 client. 148 | 149 | :rtype: list 150 | :return: List of journal blocks. 151 | """ 152 | export_configuration = describe_journal_export_result.get('S3ExportConfiguration') 153 | prefix = export_configuration.get('Prefix') 154 | bucket_name = export_configuration.get('Bucket') 155 | response = s3_client.list_objects_v2(Bucket=bucket_name, Prefix=prefix) 156 | objects = response['Contents'] 157 | logger.info('Found the following objects for list from S3:') 158 | for obj in objects: 159 | logger.info(obj['Key']) 160 | 161 | # Validate initial manifest file was written 162 | expected_manifest_key = "{}{}.started.manifest".format(prefix, describe_journal_export_result.get('ExportId')) 163 | initial_manifest = filter_for_initial_manifest(objects, expected_manifest_key) 164 | logger.info('Found the initial manifest with key: {}.'.format(initial_manifest)) 165 | 166 | # Find the final manifest file, it should contain the exportId in it. 167 | completed_manifest_file_key = filter_for_completed_manifest(objects) 168 | completed_manifest_object = s3_client.get_object(Bucket=bucket_name, Key=completed_manifest_file_key)['Body']\ 169 | .read().decode('utf-8') 170 | 171 | data_file_keys = get_data_file_keys_from_manifest(completed_manifest_object) 172 | 173 | logger.info('Found the following keys in the manifest files: {}.'.format(json_dumps(data_file_keys, indent=4))) 174 | journal_blocks = [] 175 | for key in data_file_keys: 176 | logger.info('Reading file with S3 key {} from bucket: {}.'.format(key, bucket_name)) 177 | s3_object = s3_client.get_object(Bucket=bucket_name, Key=key)['Body'].read().decode('utf-8') 178 | blocks = get_journal_blocks(s3_object) 179 | compare_key_with_content_range(key, blocks[0], blocks[len(blocks) - 1]) 180 | # `blocks` is also a list of journal blocks, so we need to concatenate them. 181 | journal_blocks.extend(blocks) 182 | return journal_blocks 183 | -------------------------------------------------------------------------------- /pyqldbsamples/list_journal_exports.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | from pyqldbsamples.constants import Constants 24 | 25 | logger = getLogger(__name__) 26 | basicConfig(level=INFO) 27 | MAX_RESULTS = 2 28 | qldb_client = client('qldb') 29 | 30 | 31 | def list_all_journal_exports(): 32 | """ 33 | List all journal exports. 34 | """ 35 | logger.info("Let's list journal exports for the AWS account.") 36 | 37 | next_token = '' 38 | list_of_results = [] 39 | while next_token is not None: 40 | if next_token == '': 41 | result = qldb_client.list_journal_s3_exports(MaxResults=MAX_RESULTS) 42 | else: 43 | result = qldb_client.list_journal_s3_exports(MaxResults=MAX_RESULTS, NextToken=next_token) 44 | next_token = result.get('NextToken') 45 | if result.get('JournalS3Exports') != []: 46 | list_of_results.append(result.get('JournalS3Exports')) 47 | logger.info('Success. List of journal exports: {}.'.format(list_of_results)) 48 | 49 | 50 | def list_journal_export_with_ledger_name(ledger_name): 51 | """ 52 | List all journal exports for the given ledger. 53 | 54 | :type ledger_name: str 55 | :param ledger_name: Name of the ledger to list journal exports for. 56 | """ 57 | logger.info("Let's list journal exports for the ledger with name: {}...".format(ledger_name)) 58 | 59 | next_token = '' 60 | list_of_results = [] 61 | while next_token is not None: 62 | if next_token == '': 63 | result = qldb_client.list_journal_s3_exports_for_ledger(Name=ledger_name, MaxResults=MAX_RESULTS) 64 | else: 65 | result = qldb_client.list_journal_s3_exports_for_ledger(Name=ledger_name, MaxResults=MAX_RESULTS, 66 | NextToken=next_token) 67 | next_token = result.get('NextToken') 68 | if result.get('JournalS3Exports') != []: 69 | list_of_results.append(result.get('JournalS3Exports')) 70 | logger.info('Success. List of journal exports: {}.'.format(list_of_results)) 71 | 72 | 73 | def main(ledger_name=Constants.LEDGER_NAME): 74 | """ 75 | List the journal exports of a given QLDB ledger. 76 | """ 77 | try: 78 | list_journal_export_with_ledger_name(ledger_name) 79 | except Exception as e: 80 | logger.exception('Unable to list exports!') 81 | raise e 82 | 83 | 84 | if __name__ == '__main__': 85 | main() 86 | -------------------------------------------------------------------------------- /pyqldbsamples/list_ledgers.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | logger = getLogger(__name__) 24 | basicConfig(level=INFO) 25 | qldb_client = client('qldb') 26 | 27 | 28 | def list_ledgers(): 29 | """ 30 | List all ledgers. 31 | 32 | :rtype: list 33 | :return: List of ledgers. 34 | """ 35 | logger.info("Let's list all the ledgers...") 36 | result = qldb_client.list_ledgers() 37 | ledgers = result.get('Ledgers') 38 | logger.info('Success. List of ledgers: {}.'.format(ledgers)) 39 | return ledgers 40 | 41 | 42 | def main(): 43 | """ 44 | List all QLDB ledgers in a given account. 45 | """ 46 | try: 47 | list_ledgers() 48 | except Exception as e: 49 | logger.exception('Unable to list ledgers!') 50 | raise e 51 | 52 | 53 | if __name__ == '__main__': 54 | main() 55 | -------------------------------------------------------------------------------- /pyqldbsamples/list_tables.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 22 | from pyqldbsamples.constants import Constants 23 | 24 | logger = getLogger(__name__) 25 | basicConfig(level=INFO) 26 | 27 | 28 | def list_tables(ledger_name): 29 | """ 30 | List all tables. 31 | 32 | :type ledger_name: str 33 | :param ledger_name: The name of the ledger. 34 | 35 | :rtype: list 36 | :return: List of tables. 37 | """ 38 | logger.info("Let's list all the tables...") 39 | with create_qldb_driver(ledger_name) as driver: 40 | logger.info("Success. List of tables:") 41 | tables = driver.list_tables() 42 | for table in tables: 43 | logger.info(table) 44 | return tables 45 | 46 | 47 | def main(ledger_name=Constants.LEDGER_NAME): 48 | """ 49 | List all the tables in the configured ledger in QLDB. 50 | """ 51 | try: 52 | list_tables(ledger_name) 53 | except Exception as e: 54 | logger.exception('Unable to list tables!') 55 | raise e 56 | 57 | 58 | if __name__ == '__main__': 59 | main() 60 | -------------------------------------------------------------------------------- /pyqldbsamples/model/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /pyqldbsamples/qldb/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | -------------------------------------------------------------------------------- /pyqldbsamples/qldb/block_address.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | 18 | def block_address_to_dictionary(ion_dict): 19 | """ 20 | Convert a block address from IonPyDict into a dictionary. 21 | Shape of the dictionary must be: {'IonText': "{strandId: <"strandId">, sequenceNo: }"} 22 | 23 | :type ion_dict: :py:class:`amazon.ion.simple_types.IonPyDict`/str 24 | :param ion_dict: The block address value to convert. 25 | 26 | :rtype: dict 27 | :return: The converted dict. 28 | """ 29 | block_address = {'IonText': {}} 30 | if not isinstance(ion_dict, str): 31 | py_dict = '{{strandId: "{}", sequenceNo:{}}}'.format(ion_dict['strandId'], ion_dict['sequenceNo']) 32 | ion_dict = py_dict 33 | block_address['IonText'] = ion_dict 34 | return block_address 35 | -------------------------------------------------------------------------------- /pyqldbsamples/qldb/journal_block.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | 17 | 18 | class JournalBlock: 19 | """ 20 | Represents a JournalBlock that was recorded after executing a transaction in the ledger. 21 | """ 22 | def __init__(self, block_address, transaction_id, block_timestamp, block_hash, entries_hash, previous_block_hash, 23 | entries_hash_list, transaction_info, redaction_info, revisions): 24 | self.block_address = block_address 25 | self.transaction_id = transaction_id 26 | self.block_timestamp = block_timestamp 27 | self.block_hash = block_hash 28 | self.entries_hash = entries_hash 29 | self.previous_block_hash = previous_block_hash 30 | self.entries_hash_list = entries_hash_list 31 | self.transaction_info = transaction_info 32 | self.redaction_info = redaction_info 33 | self.revisions = revisions 34 | 35 | 36 | def from_ion(ion_value): 37 | """ 38 | Construct a new JournalBlock object from an IonStruct. 39 | 40 | :type ion_value: :py:class:`amazon.ion.simple_types.IonSymbol` 41 | :param ion_value: The IonStruct returned by QLDB that represents a journal block. 42 | 43 | :rtype: :py:class:`pyqldbsamples.qldb.journal_block.JournalBlock` 44 | :return: The constructed JournalBlock object. 45 | """ 46 | block_address = ion_value.get('blockAddress') 47 | transaction_id = ion_value.get('transactionId') 48 | block_timestamp = ion_value.get('blockTimestamp') 49 | block_hash = ion_value.get('blockHash') 50 | entries_hash = ion_value.get('entriesHash') 51 | previous_block_hash = ion_value.get('previousBlockHash') 52 | entries_hash_list = ion_value.get('entriesHashList') 53 | transaction_info = ion_value.get('transactionInfo') 54 | redaction_info = ion_value.get('redactionInfo') 55 | revisions = ion_value.get('revisions') 56 | 57 | journal_block = JournalBlock(block_address, transaction_id, block_timestamp, block_hash, entries_hash, 58 | previous_block_hash, entries_hash_list, transaction_info, redaction_info, revisions) 59 | return journal_block 60 | -------------------------------------------------------------------------------- /pyqldbsamples/qldb/qldb_string_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | from amazon.ion.simpleion import dumps, loads 17 | 18 | 19 | def value_holder_to_string(value_holder): 20 | """ 21 | Returns the string representation of a given `value_holder`. 22 | 23 | :type value_holder: dict 24 | :param value_holder: The `value_holder` to convert to string. 25 | 26 | :rtype: str 27 | :return: The string representation of the supplied `value_holder`. 28 | """ 29 | ret_val = dumps(loads(value_holder), binary=False, indent=' ', omit_version_marker=True) 30 | val = '{{ IonText: {}}}'.format(ret_val) 31 | return val 32 | 33 | 34 | def block_response_to_string(block_response): 35 | """ 36 | Returns the string representation of a given `block_response`. 37 | 38 | :type block_response: dict 39 | :param block_response: The `block_response` to convert to string. 40 | 41 | :rtype: str 42 | :return: The string representation of the supplied `block_response`. 43 | """ 44 | string = '' 45 | if block_response.get('Block', {}).get('IonText') is not None: 46 | string += 'Block: ' + value_holder_to_string(block_response['Block']['IonText']) + ', ' 47 | 48 | if block_response.get('Proof', {}).get('IonText') is not None: 49 | string += 'Proof: ' + value_holder_to_string(block_response['Proof']['IonText']) 50 | 51 | return '{' + string + '}' 52 | 53 | 54 | def digest_response_to_string(digest_response): 55 | """ 56 | Returns the string representation of a given `digest_response`. 57 | 58 | :type digest_response: dict 59 | :param digest_response: The `digest_response` to convert to string. 60 | 61 | :rtype: str 62 | :return: The string representation of the supplied `digest_response`. 63 | """ 64 | string = '' 65 | if digest_response.get('Digest') is not None: 66 | string += 'Digest: ' + str(digest_response['Digest']) + ', ' 67 | 68 | if digest_response.get('DigestTipAddress', {}).get('IonText') is not None: 69 | string += 'DigestTipAddress: ' + value_holder_to_string(digest_response['DigestTipAddress']['IonText']) 70 | 71 | return '{' + string + '}' 72 | -------------------------------------------------------------------------------- /pyqldbsamples/query_history.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from datetime import datetime, timedelta 20 | from logging import basicConfig, getLogger, INFO 21 | 22 | from pyqldbsamples.model.sample_data import print_result, get_document_ids, SampleData 23 | from pyqldbsamples.constants import Constants 24 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | 29 | 30 | def format_date_time(date_time): 31 | """ 32 | Format the given date time to a string. 33 | 34 | :type date_time: :py:class:`datetime.datetime` 35 | :param date_time: The date time to format. 36 | 37 | :rtype: str 38 | :return: The formatted date time. 39 | """ 40 | return date_time.strftime('`%Y-%m-%dT%H:%M:%S.%fZ`') 41 | 42 | 43 | def previous_primary_owners(driver, vin): 44 | """ 45 | Find previous primary owners for the given VIN in a single transaction. 46 | In this example, query the `VehicleRegistration` history table to find all previous primary owners for a VIN. 47 | 48 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 49 | :param driver: An instance of the QldbDriver class. 50 | 51 | :type vin: str 52 | :param vin: VIN to find previous primary owners for. 53 | """ 54 | person_ids = driver.execute_lambda(lambda executor: get_document_ids(executor, 55 | Constants.VEHICLE_REGISTRATION_TABLE_NAME, 56 | 'VIN', vin)) 57 | 58 | todays_date = datetime.utcnow() - timedelta(seconds=1) 59 | three_months_ago = todays_date - timedelta(days=90) 60 | query = 'SELECT data.Owners.PrimaryOwner, metadata.version FROM history({}, {}, {}) AS h WHERE h.metadata.id = ?'.\ 61 | format(Constants.VEHICLE_REGISTRATION_TABLE_NAME, format_date_time(three_months_ago), 62 | format_date_time(todays_date)) 63 | 64 | for ids in person_ids: 65 | logger.info("Querying the 'VehicleRegistration' table's history using VIN: {}.".format(vin)) 66 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, ids)) 67 | if not (print_result(cursor)) > 0: 68 | logger.info('No modification history found within the given time frame for document ID: {}'.format(ids)) 69 | 70 | 71 | def main(ledger_name=Constants.LEDGER_NAME): 72 | """ 73 | Query a table's history for a particular set of documents. 74 | """ 75 | try: 76 | with create_qldb_driver(ledger_name) as driver: 77 | vin = SampleData.VEHICLE_REGISTRATION[0]['VIN'] 78 | previous_primary_owners(driver, vin) 79 | logger.info('Successfully queried history.') 80 | except Exception as e: 81 | logger.exception('Unable to query history to find previous owners.') 82 | raise e 83 | 84 | 85 | if __name__ == '__main__': 86 | main() 87 | -------------------------------------------------------------------------------- /pyqldbsamples/redact_revision.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | from time import sleep 21 | 22 | from pyqldbsamples.add_secondary_owner import get_document_ids, SampleData 23 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 24 | from pyqldbsamples.constants import Constants 25 | from pyqldbsamples.model.sample_data import print_ion 26 | from pyqldbsamples.transfer_vehicle_ownership import validate_and_update_registration 27 | 28 | logger = getLogger(__name__) 29 | basicConfig(level=INFO) 30 | 31 | 32 | def get_table_id(transaction_executor, table_name): 33 | """ 34 | Get the tableId of a table with the given table name. 35 | 36 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 37 | :param transaction_executor: An Executor object allowing for execution of statements 38 | within a transaction. 39 | 40 | :type table_name: :py:class:`amazon.ion.simple_types.IonPyText` 41 | :param table_name: The table name for which we'll get the tableId. 42 | 43 | :rtype: :py:class:`amazon.ion.simple_types.IonPyText` 44 | :return: The tableId for the provided table name. 45 | """ 46 | logger.info("Getting the tableId for table with name: {}".format(table_name)) 47 | query = "SELECT VALUE tableId FROM information_schema.user_tables WHERE name = '{}'".format(table_name) 48 | results = list(transaction_executor.execute_statement(query)) 49 | logger.info("Results list: {}".format(results)) 50 | if len(results) == 0: 51 | raise Exception("Unable to find table with name {}".format(table_name)) 52 | return results[0] 53 | 54 | 55 | def get_historic_registration_by_owner_id(transaction_executor, registration_document_id, owner_document_id): 56 | """ 57 | Get the historic revision of a vehicle registration with the given ownerId. 58 | 59 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 60 | :param transaction_executor: An Executor object allowing for execution of statements 61 | within a transaction. 62 | 63 | :type registration_document_id: :py:class:`amazon.ion.simple_types.IonPyText` 64 | :param registration_document_id: The QLDB DocumentId of the vehicle registration document. 65 | 66 | :type owner_document_id: :py:class:`amazon.ion.simple_types.IonPyText` 67 | :param owner_document_id: The QLDB DocumentId of the vehicle owner. 68 | 69 | :rtype :py:class:`amazon.ion.simple_types.IonPySymbol` 70 | :return An Ion Struct returned by QLDB that represents a document revision. 71 | """ 72 | logger.info( 73 | "Querying the 'VehicleRegistration' table's history for a registration with documentId: {} and owner: {}" 74 | .format(registration_document_id, owner_document_id) 75 | ) 76 | query = "SELECT * FROM history({}) AS h WHERE h.metadata.id = '{}' AND h.data.Owners.PrimaryOwner.PersonId = '{}'" \ 77 | .format(Constants.VEHICLE_REGISTRATION_TABLE_NAME, registration_document_id, owner_document_id) 78 | results = list(transaction_executor.execute_statement(query)) 79 | if len(results) == 0: 80 | raise Exception( 81 | "Unable to find historic registration with documentId: {} and ownerId: {}" 82 | .format(registration_document_id, owner_document_id) 83 | ) 84 | elif len(results) > 1: 85 | raise Exception( 86 | "Found more than 1 historic registrations with documentId: {} and ownerId: {}" 87 | .format(registration_document_id, owner_document_id) 88 | ) 89 | result = results[0] 90 | print_ion(result) 91 | return result 92 | 93 | 94 | def get_historic_registration_by_version_number(transaction_executor, registration_document_id, version_number): 95 | """ 96 | Get the historic revision of a vehicle registration with the given document version. 97 | 98 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 99 | :param transaction_executor: An Executor object allowing for execution of statements 100 | within a transaction. 101 | 102 | :type registration_document_id: :py:class:`amazon.ion.simple_types.IonPyText` 103 | :param registration_document_id: The QLDB DocumentId of the vehicle registration document. 104 | 105 | :type version_number: :py:class:`amazon.ion.simple_types.IonPyInt` 106 | :param version_number: The version of the vehicle registration document to query the history for. 107 | 108 | :rtype :py:class:`amazon.ion.simple_types.IonPySymbol` 109 | :return An Ion Struct returned by QLDB that represents a document revision. 110 | """ 111 | logger.info( 112 | "Querying the 'VehicleRegistration' table's history for a registration with documentId: {} and version: {}" 113 | .format(registration_document_id, version_number) 114 | ) 115 | query = "SELECT * FROM history({}) AS h WHERE h.metadata.id = '{}' AND h.metadata.version = {}" \ 116 | .format(Constants.VEHICLE_REGISTRATION_TABLE_NAME, registration_document_id, version_number) 117 | results = list(transaction_executor.execute_statement(query)) 118 | if len(results) == 0: 119 | raise Exception( 120 | "Unable to find historic registration with documentId: {} and version: {}" 121 | .format(registration_document_id, version_number) 122 | ) 123 | result = results[0] 124 | print_ion(result) 125 | return result 126 | 127 | 128 | def redact_previous_registration(transaction_executor, vin, previous_owner_gov_id): 129 | """ 130 | Redact a historic revision of a vehicle registration. 131 | 132 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 133 | :param transaction_executor: An Executor object allowing for execution of statements 134 | within a transaction. 135 | 136 | :type vin: :py:class:`amazon.ion.simple_types.IonPyText` 137 | :param vin: The VIN specified in the vehicle registration. 138 | 139 | :type previous_owner_gov_id: :py:class:`amazon.ion.simple_types.IonPyText` 140 | :param previous_owner_gov_id: The OwnerId on the previous revision of the 141 | vehicle registration that is going to be redacted. 142 | 143 | :rtype :py:class:`amazon.ion.simple_types.IonPySymbol` 144 | :return An Ion Struct returned as a response to a redaction request. 145 | """ 146 | table_id = get_table_id(transaction_executor, Constants.VEHICLE_REGISTRATION_TABLE_NAME) 147 | registration_document_id = \ 148 | get_document_ids(transaction_executor, Constants.VEHICLE_REGISTRATION_TABLE_NAME, 'VIN', vin)[0] 149 | previous_owner_document_id = \ 150 | get_document_ids(transaction_executor, Constants.PERSON_TABLE_NAME, 'GovId', previous_owner_gov_id)[0] 151 | historic_revision_block_address = get_historic_registration_by_owner_id( 152 | transaction_executor, registration_document_id, previous_owner_document_id 153 | )['blockAddress'] 154 | 155 | logger.info("Redacting the revision at blockAddress: {} with tableId: {} and documentId: {}" 156 | .format(historic_revision_block_address, table_id, registration_document_id)) 157 | redact_query = "EXEC redact_revision ?, '{}', '{}'".format(table_id, registration_document_id) 158 | redact_request = next(transaction_executor.execute_statement(redact_query, historic_revision_block_address)) 159 | print_ion(redact_request) 160 | return redact_request 161 | 162 | 163 | def wait_till_revision_redacted(driver, redact_request): 164 | """ 165 | Wait until a revision is redacted by checking the history of the document. 166 | 167 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 168 | :param driver: An instance of the QldbDriver class. 169 | 170 | :type redact_request: :py:class:`amazon.ion.simple_types.IonPySymbol` 171 | :param redact_request: An Ion Struct returned as a response to a redaction request. 172 | """ 173 | is_redacted = False 174 | while not is_redacted: 175 | revision = driver.execute_lambda(lambda executor: get_historic_registration_by_version_number( 176 | executor, redact_request['documentId'], redact_request['version'] 177 | )) 178 | if 'data' not in revision and 'dataHash' in revision: 179 | is_redacted = True 180 | logger.info("Revision was successfully redacted.") 181 | else: 182 | logger.info("Revision is not yet redacted. Waiting for sometime...") 183 | sleep(10) 184 | 185 | 186 | def main(ledger_name=Constants.LEDGER_NAME): 187 | """ 188 | Redact a historic revision of a vehicle registration document. 189 | """ 190 | vehicle_vin = SampleData.VEHICLE[2]['VIN'] 191 | previous_owner = SampleData.PERSON[2]['GovId'] 192 | new_owner = SampleData.PERSON[3]['GovId'] 193 | 194 | try: 195 | with create_qldb_driver(ledger_name) as driver: 196 | validate_and_update_registration(driver, vehicle_vin, previous_owner, new_owner) 197 | redact_request = driver.execute_lambda( 198 | lambda executor: redact_previous_registration(executor, vehicle_vin, previous_owner)) 199 | wait_till_revision_redacted(driver, redact_request) 200 | except Exception as e: 201 | logger.exception('Error redacting VehicleRegistration.') 202 | raise e 203 | 204 | 205 | if __name__ == '__main__': 206 | main() 207 | -------------------------------------------------------------------------------- /pyqldbsamples/register_drivers_license.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | from datetime import datetime 21 | 22 | from pyqldbsamples.model.sample_data import convert_object_to_ion, get_document_ids 23 | from pyqldbsamples.constants import Constants 24 | from pyqldbsamples.insert_document import insert_documents 25 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 26 | 27 | logger = getLogger(__name__) 28 | basicConfig(level=INFO) 29 | 30 | 31 | def person_already_exists(transaction_executor, gov_id): 32 | """ 33 | Verify whether a driver already exists in the database. 34 | 35 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 36 | :param transaction_executor: An Executor object allowing for execution of statements within a transaction. 37 | 38 | :type gov_id: str 39 | :param gov_id: The government ID to search `Person` table against. 40 | 41 | :rtype: bool 42 | :return: If the Person has been registered. 43 | """ 44 | query = 'SELECT * FROM Person AS p WHERE p.GovId = ?' 45 | cursor = transaction_executor.execute_statement(query, convert_object_to_ion(gov_id)) 46 | try: 47 | next(cursor) 48 | return True 49 | except StopIteration: 50 | return False 51 | 52 | 53 | def person_has_drivers_license(transaction_executor, document_id): 54 | """ 55 | Check if the driver already has a driver's license using their unique document ID. 56 | 57 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 58 | :param transaction_executor: An Executor object allowing for execution of statements within a transaction. 59 | 60 | :type document_id: str 61 | :param document_id: The document ID to check. 62 | 63 | :rtype: bool 64 | :return: If the Person has a drivers license. 65 | """ 66 | cursor = lookup_drivers_license_for_person(transaction_executor, document_id) 67 | try: 68 | next(cursor) 69 | return True 70 | except StopIteration: 71 | return False 72 | 73 | 74 | def lookup_drivers_license_for_person(transaction_executor, person_id): 75 | """ 76 | Query drivers license table by person ID. 77 | 78 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 79 | :param transaction_executor: An Executor object allowing for execution of statements within a transaction. 80 | 81 | :type person_id: str 82 | :param person_id: The person ID to check. 83 | 84 | :rtype: :py:class:`pyqldb.cursor.stream_cursor.StreamCursor` 85 | :return: Cursor on the result set of a statement query. 86 | """ 87 | query = 'SELECT * FROM DriversLicense AS d WHERE d.PersonId = ?' 88 | cursor = transaction_executor.execute_statement(query, person_id) 89 | return cursor 90 | 91 | 92 | def register_new_person(driver, person): 93 | """ 94 | Register a new person in QLDB if not already registered. 95 | 96 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 97 | :param driver: An instance of the QldbDriver class. 98 | 99 | :type person: dict 100 | :param person: The person to register. 101 | 102 | :rtype: str 103 | :return: The person ID. 104 | """ 105 | gov_id = person['GovId'] 106 | if driver.execute_lambda(lambda executor: person_already_exists(executor, gov_id)): 107 | logger.info('Person with this GovId already exists.') 108 | 109 | result = driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME, 110 | 'GovId', gov_id)) 111 | result = result[0] 112 | else: 113 | result = insert_documents(driver, Constants.PERSON_TABLE_NAME, [person]) 114 | result = result[0] 115 | return result 116 | 117 | 118 | def register_new_drivers_license(driver, person, new_license): 119 | """ 120 | Register a new person and a new driver's license. 121 | 122 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 123 | :param driver: An instance of the QldbDriver class. 124 | 125 | :type person: dict 126 | :param person: The person to register. 127 | 128 | :type new_license: dict 129 | :param new_license: The driver's license to register. 130 | """ 131 | person_id = register_new_person(driver, person) 132 | if driver.execute_lambda(lambda executor: person_has_drivers_license(executor, person_id)): 133 | gov_id = person['GovId'] 134 | logger.info("Person with government ID '{}' already has a license! No new license added.".format(gov_id)) 135 | else: 136 | logger.info("Registering new driver's license...") 137 | # Update the new license with new driver's unique PersonId. 138 | new_license.update({'PersonId': str(person_id)}) 139 | statement = 'INSERT INTO DriversLicense ?' 140 | driver.execute_lambda(lambda executor: executor.execute_statement(statement, convert_object_to_ion(new_license))) 141 | 142 | cursor = driver.execute_lambda(lambda executor: lookup_drivers_license_for_person(executor, person_id)) 143 | try: 144 | next(cursor) 145 | logger.info('Successfully registered new driver.') 146 | return 147 | except StopIteration: 148 | logger.info('Problem occurred while inserting new license, please review the results.') 149 | return 150 | 151 | 152 | def main(ledger_name=Constants.LEDGER_NAME): 153 | """ 154 | Register a new driver's license. 155 | """ 156 | try: 157 | with create_qldb_driver(ledger_name) as driver: 158 | person = { 159 | 'FirstName': 'Kate', 160 | 'LastName': 'Mulberry', 161 | 'Address': '22 Commercial Drive, Blaine, WA, 97722', 162 | 'DOB': datetime(1995, 2, 9), 163 | 'GovId': 'AQQ17B2342', 164 | 'GovIdType': 'Passport' 165 | } 166 | drivers_license = { 167 | 'PersonId': '', 168 | 'LicenseNumber': '112 360 PXJ', 169 | 'LicenseType': 'Full', 170 | 'ValidFromDate': datetime(2018, 6, 30), 171 | 'ValidToDate': datetime(2022, 10, 30) 172 | } 173 | 174 | register_new_drivers_license(driver, person, drivers_license) 175 | except Exception as e: 176 | logger.exception('Error registering new driver.') 177 | raise e 178 | 179 | 180 | if __name__ == '__main__': 181 | main() 182 | -------------------------------------------------------------------------------- /pyqldbsamples/renew_drivers_license.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | from datetime import datetime 21 | 22 | from pyqldbsamples.model.sample_data import SampleData, convert_object_to_ion, get_document_ids_from_dml_results 23 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 24 | from pyqldbsamples.constants import Constants 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | 29 | VALID_FROM_DATE = datetime(2019, 4, 19) 30 | VALID_TO_DATE = datetime(2023, 4, 19) 31 | 32 | 33 | def verify_driver_from_license_number(driver, license_number): 34 | """ 35 | Verify whether a driver exists in the system with the provided license number. 36 | 37 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 38 | :param driver: An instance of the QldbDriver class. 39 | 40 | :type license_number: str 41 | :param license_number: The driver's license number. 42 | 43 | :raises RuntimeError: If driver does not exist in the system. 44 | """ 45 | logger.info("Finding person ID with license number: {}.".format(license_number)) 46 | query = 'SELECT PersonId FROM DriversLicense AS d WHERE d.LicenseNumber = ?' 47 | person_id = driver.execute_lambda(lambda executor: executor.execute_statement(query, 48 | convert_object_to_ion(license_number))) 49 | pid = next(person_id).get('PersonId') 50 | query = 'SELECT p.* FROM Person AS p BY pid WHERE pid = ?' 51 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, pid)) 52 | try: 53 | next(cursor) 54 | except StopIteration: 55 | raise RuntimeError('Unable to find person with ID: {}'.format(pid)) 56 | 57 | 58 | def renew_drivers_license(driver, valid_from, valid_to, license_number): 59 | """ 60 | Renew the ValidFromDate and ValidToDate of a driver's license. 61 | 62 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 63 | :param driver: An instance of the QldbDriver class. 64 | 65 | :type valid_from: :py:class:`datetime.datetime` 66 | :param valid_from: The new valid-from date. 67 | 68 | :type valid_to: :py:class:`datetime.datetime` 69 | :param valid_to: The new valid-to date. 70 | 71 | :type license_number: str 72 | :param license_number: The license number for the driver's license to renew. 73 | 74 | :raises RuntimeError: If no driver's license was updated. 75 | """ 76 | logger.info('Renewing license with license number: {}...'.format(license_number)) 77 | update_valid_date = 'UPDATE DriversLicense AS d SET d.ValidFromDate = ?, d.ValidToDate = ? WHERE d.LicenseNumber ' \ 78 | '= ?' 79 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(update_valid_date, 80 | convert_object_to_ion(valid_from), 81 | convert_object_to_ion(valid_to), 82 | convert_object_to_ion(license_number))) 83 | logger.info('DriversLicense Document IDs which had licenses renewed: ') 84 | list_of_licenses = get_document_ids_from_dml_results(cursor) 85 | for license in list_of_licenses: 86 | logger.info(license) 87 | return list_of_licenses 88 | 89 | 90 | def verify_and_renew_license(driver, license_num, valid_from_date, valid_to_date): 91 | """ 92 | Verify if the driver of the given license and update the license with the given dates. 93 | 94 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 95 | :param driver: An instance of the QldbDriver class. 96 | 97 | :type license_num: str 98 | :param license_num: The license to verify and renew. 99 | 100 | :type valid_from_date: :py:class:`datetime.datetime` 101 | :param valid_from_date: The new valid-from date. 102 | 103 | :type valid_to_date: :py:class:`datetime.datetime` 104 | :param valid_to_date: The new valid-to date. 105 | """ 106 | verify_driver_from_license_number(driver, license_num) 107 | renew_drivers_license(driver, valid_from_date, valid_to_date, license_num) 108 | 109 | 110 | def main(ledger_name=Constants.LEDGER_NAME): 111 | """ 112 | Find the person associated with a license number. 113 | Renew a driver's license. 114 | """ 115 | try: 116 | with create_qldb_driver(ledger_name) as driver: 117 | license_number = SampleData.DRIVERS_LICENSE[0]['LicenseNumber'] 118 | verify_and_renew_license(driver, license_number, VALID_FROM_DATE, VALID_TO_DATE) 119 | except Exception as e: 120 | logger.exception('Error renewing drivers license.') 121 | raise e 122 | 123 | 124 | if __name__ == '__main__': 125 | main() 126 | -------------------------------------------------------------------------------- /pyqldbsamples/scan_table.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.model.sample_data import print_result 22 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 23 | from pyqldbsamples.constants import Constants 24 | 25 | logger = getLogger(__name__) 26 | basicConfig(level=INFO) 27 | 28 | 29 | def scan_table(driver, table_name): 30 | """ 31 | Scan for all the documents in a table. 32 | 33 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 34 | :param driver: An instance of the QldbDriver class. 35 | 36 | :type table_name: str 37 | :param table_name: The name of the table to operate on. 38 | 39 | :rtype: :py:class:`pyqldb.cursor.buffered_cursor.BufferedCursor` 40 | :return: Cursor on the result set of a statement query. 41 | """ 42 | logger.info('Scanning {}...'.format(table_name)) 43 | query = 'SELECT * FROM {}'.format(table_name) 44 | return driver.execute_lambda(lambda executor: executor.execute_statement(query)) 45 | 46 | 47 | def main(ledger_name=Constants.LEDGER_NAME): 48 | """ 49 | Scan for all the documents in a table. 50 | """ 51 | try: 52 | with create_qldb_driver(ledger_name) as driver: 53 | # Scan all the tables and print their documents. 54 | tables = driver.list_tables() 55 | for table in tables: 56 | cursor = scan_table(driver, table) 57 | logger.info('Scan successful!') 58 | print_result(cursor) 59 | except Exception as e: 60 | logger.exception('Unable to scan tables.') 61 | raise e 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /pyqldbsamples/tag_resource.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from boto3 import client 22 | 23 | from pyqldbsamples.constants import Constants 24 | from pyqldbsamples.create_ledger import wait_for_active 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | qldb_client = client('qldb') 29 | 30 | ADD_TAGS = {'Domain': 'Prod'} 31 | CREATE_TAGS = {'IsTest': 'true', 'Domain': 'Test'} 32 | REMOVE_TAGS = ['IsTest'] 33 | 34 | 35 | def create_with_tags(name, tags): 36 | """ 37 | Create a ledger with the specified name and the given tags. 38 | 39 | :type name: str 40 | :param name: Name of the ledger to be created. 41 | 42 | :type tags: dict 43 | :param tags: A dictionary of tags to create the ledger with. 44 | 45 | :rtype: dict 46 | :return: The result returned by the database. 47 | """ 48 | logger.info("Let's create the ledger with name: {}...".format(name)) 49 | result = qldb_client.create_ledger(Name=name, Tags=tags, PermissionsMode="ALLOW_ALL") 50 | logger.info('Success. Ledger state: {}.'.format(result.get('State'))) 51 | return result 52 | 53 | 54 | def tag_resource(resource_arn, tags): 55 | """ 56 | Add one or more tags to the specified QLDB resource. 57 | 58 | :type resource_arn: dict 59 | :param resource_arn: The Amazon Resource Name (ARN) of the ledger to which to add tags. 60 | 61 | :type tags: dict 62 | :param tags: The key-value pairs to add as tags. 63 | """ 64 | logger.info("Let's add tags {} for resource with arn: {}...".format(tags, resource_arn)) 65 | qldb_client.tag_resource(ResourceArn=resource_arn, Tags=tags) 66 | logger.info('Successfully added tags.') 67 | 68 | 69 | def untag_resource(resource_arn, tag_keys): 70 | """ 71 | Remove one or more tags from the specified QLDB resource. 72 | 73 | :type resource_arn: dict 74 | :param resource_arn: The Amazon Resource Name (ARN) of the ledger from which to remove the tags. 75 | 76 | :type tag_keys: list 77 | :param tag_keys: The list of tag keys to remove. 78 | """ 79 | logger.info("Let's remove tags {} for resource with arn: {}.".format(tag_keys, resource_arn)) 80 | qldb_client.untag_resource(ResourceArn=resource_arn, TagKeys=tag_keys) 81 | logger.info('Successfully removed tags.') 82 | 83 | 84 | def list_tags(resource_arn): 85 | """ 86 | Returns all tags for a specified Amazon QLDB resource. 87 | 88 | :type resource_arn: dict 89 | :param resource_arn: The Amazon Resource Name (ARN) for which to list tags off. 90 | 91 | :rtype: dict 92 | :return: All tags on the specified resource. 93 | """ 94 | logger.info("Let's list the tags for resource with arn: {}.".format(resource_arn)) 95 | result = qldb_client.list_tags_for_resource(ResourceArn=resource_arn) 96 | logger.info('Success. Tags: {}.'.format(result.get('Tags'))) 97 | return result 98 | 99 | 100 | def main(ledger_name=Constants.LEDGER_NAME_WITH_TAGS): 101 | """ 102 | Tagging and un-tagging resources, including tag on create. 103 | """ 104 | try: 105 | result = create_with_tags(ledger_name, CREATE_TAGS) 106 | wait_for_active(ledger_name) 107 | ARN = result.get('Arn') 108 | list_tags(ARN) 109 | untag_resource(ARN, REMOVE_TAGS) 110 | list_tags(ARN) 111 | tag_resource(ARN, ADD_TAGS) 112 | list_tags(ARN) 113 | except Exception as e: 114 | logger.exception('Unable to tag or untag resources!') 115 | raise e 116 | 117 | 118 | if __name__ == '__main__': 119 | main() 120 | -------------------------------------------------------------------------------- /pyqldbsamples/transfer_vehicle_ownership.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from logging import basicConfig, getLogger, INFO 20 | 21 | from pyqldbsamples.add_secondary_owner import get_document_ids, print_result, SampleData 22 | from pyqldbsamples.constants import Constants 23 | from pyqldbsamples.model.sample_data import convert_object_to_ion 24 | from pyqldbsamples.connect_to_ledger import create_qldb_driver 25 | 26 | logger = getLogger(__name__) 27 | basicConfig(level=INFO) 28 | 29 | 30 | def find_person_from_document_id(transaction_executor, document_id): 31 | """ 32 | Query a driver's information using the given ID. 33 | 34 | :type transaction_executor: :py:class:`pyqldb.execution.executor.Executor` 35 | :param transaction_executor: An Executor object allowing for execution of statements within a transaction. 36 | 37 | :type document_id: :py:class:`amazon.ion.simple_types.IonPyText` 38 | :param document_id: The document ID required to query for the person. 39 | 40 | :rtype: :py:class:`amazon.ion.simple_types.IonPyDict` 41 | :return: The resulting document from the query. 42 | """ 43 | query = 'SELECT p.* FROM Person AS p BY pid WHERE pid = ?' 44 | cursor = transaction_executor.execute_statement(query, document_id) 45 | return next(cursor) 46 | 47 | 48 | def find_primary_owner_for_vehicle(driver, vin): 49 | """ 50 | Find the primary owner of a vehicle given its VIN. 51 | 52 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 53 | :param driver: An instance of the QldbDriver class. 54 | 55 | :type vin: str 56 | :param vin: The VIN to find primary owner for. 57 | 58 | :rtype: :py:class:`amazon.ion.simple_types.IonPyDict` 59 | :return: The resulting document from the query. 60 | """ 61 | logger.info('Finding primary owner for vehicle with VIN: {}.'.format(vin)) 62 | query = "SELECT Owners.PrimaryOwner.PersonId FROM VehicleRegistration AS v WHERE v.VIN = ?" 63 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(query, convert_object_to_ion(vin))) 64 | try: 65 | return driver.execute_lambda(lambda executor: find_person_from_document_id(executor, 66 | next(cursor).get('PersonId'))) 67 | except StopIteration: 68 | logger.error('No primary owner registered for this vehicle.') 69 | return None 70 | 71 | 72 | def update_vehicle_registration(driver, vin, document_id): 73 | """ 74 | Update the primary owner for a vehicle using the given VIN. 75 | 76 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 77 | :param driver: An instance of the QldbDriver class. 78 | 79 | :type vin: str 80 | :param vin: The VIN for the vehicle to operate on. 81 | 82 | :type document_id: :py:class:`amazon.ion.simple_types.IonPyText` 83 | :param document_id: New PersonId for the primary owner. 84 | 85 | :raises RuntimeError: If no vehicle registration was found using the given document ID and VIN. 86 | """ 87 | logger.info('Updating the primary owner for vehicle with Vin: {}...'.format(vin)) 88 | statement = "UPDATE VehicleRegistration AS r SET r.Owners.PrimaryOwner.PersonId = ? WHERE r.VIN = ?" 89 | cursor = driver.execute_lambda(lambda executor: executor.execute_statement(statement, document_id, 90 | convert_object_to_ion(vin))) 91 | try: 92 | print_result(cursor) 93 | logger.info('Successfully transferred vehicle with VIN: {} to new owner.'.format(vin)) 94 | except StopIteration: 95 | raise RuntimeError('Unable to transfer vehicle, could not find registration.') 96 | 97 | 98 | def validate_and_update_registration(driver, vin, current_owner, new_owner): 99 | """ 100 | Validate the current owner of the given vehicle and transfer its ownership to a new owner. 101 | 102 | :type driver: :py:class:`pyqldb.driver.qldb_driver.QldbDriver` 103 | :param driver: An instance of the QldbDriver class. 104 | 105 | :type vin: str 106 | :param vin: The VIN of the vehicle to transfer ownership of. 107 | 108 | :type current_owner: str 109 | :param current_owner: The GovId of the current owner of the vehicle. 110 | 111 | :type new_owner: str 112 | :param new_owner: The GovId of the new owner of the vehicle. 113 | 114 | :raises RuntimeError: If unable to verify primary owner. 115 | """ 116 | primary_owner = find_primary_owner_for_vehicle(driver, vin) 117 | if primary_owner is None or primary_owner['GovId'] != current_owner: 118 | raise RuntimeError('Incorrect primary owner identified for vehicle, unable to transfer.') 119 | 120 | document_ids = driver.execute_lambda(lambda executor: get_document_ids(executor, Constants.PERSON_TABLE_NAME, 121 | 'GovId', new_owner)) 122 | update_vehicle_registration(driver, vin, document_ids[0]) 123 | 124 | 125 | def main(ledger_name=Constants.LEDGER_NAME): 126 | """ 127 | Find primary owner for a particular vehicle's VIN. 128 | Transfer to another primary owner for a particular vehicle's VIN. 129 | """ 130 | vehicle_vin = SampleData.VEHICLE[0]['VIN'] 131 | previous_owner = SampleData.PERSON[0]['GovId'] 132 | new_owner = SampleData.PERSON[1]['GovId'] 133 | 134 | try: 135 | with create_qldb_driver(ledger_name) as driver: 136 | validate_and_update_registration(driver, vehicle_vin, previous_owner, new_owner) 137 | except Exception as e: 138 | logger.exception('Error updating VehicleRegistration.') 139 | raise e 140 | 141 | 142 | if __name__ == '__main__': 143 | main() 144 | -------------------------------------------------------------------------------- /pyqldbsamples/validate_qldb_hash_chain.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from functools import reduce 20 | from logging import basicConfig, getLogger, INFO 21 | from sys import argv 22 | import time 23 | 24 | from boto3 import client, resource 25 | 26 | from pyqldbsamples.constants import Constants 27 | from pyqldbsamples.describe_journal_export import describe_journal_export 28 | from pyqldbsamples.export_journal import create_export_and_wait_for_completion, create_s3_bucket_if_not_exists, \ 29 | set_up_s3_encryption_configuration 30 | from pyqldbsamples.journal_s3_export_reader import read_export 31 | from pyqldbsamples.verifier import join_hash_pairwise 32 | 33 | logger = getLogger(__name__) 34 | basicConfig(level=INFO) 35 | 36 | 37 | def create_journal_export(): 38 | """ 39 | Export journal contents to a S3 bucket. 40 | 41 | :rtype: str 42 | :return: The ExportId fo the journal export. 43 | """ 44 | s3_resource = resource('s3') 45 | sts = client('sts') 46 | 47 | current_time = int(time.time()) 48 | identity = sts.get_caller_identity() 49 | bucket_name = '{}-{}'.format(Constants.JOURNAL_EXPORT_S3_BUCKET_NAME_PREFIX, identity['Account']) 50 | 51 | prefix = '{}-{}/'.format(Constants.LEDGER_NAME, current_time) 52 | create_s3_bucket_if_not_exists(bucket_name, s3_resource) 53 | 54 | export_journal_to_s3_result = create_export_and_wait_for_completion(Constants.LEDGER_NAME, bucket_name, prefix, 55 | set_up_s3_encryption_configuration()) 56 | 57 | return export_journal_to_s3_result.get('ExportId') 58 | 59 | 60 | def compare_journal_blocks(previous_journal_block, journal_block): 61 | """ 62 | Compare the hash values on the given journal blocks. 63 | 64 | :type previous_journal_block: :py:class:`pyqldbsamples.qldb.journal_block.JournalBlock` 65 | :param previous_journal_block: Previous journal block in the chain. 66 | 67 | :type journal_block: :py:class:`pyqldbsamples.qldb.journal_block.JournalBlock` 68 | :param journal_block: Current journal block in the chain. 69 | 70 | :rtype: :py:class:`pyqldbsamples.qldb.journal_block.JournalBlock` 71 | :return: The current journal block in the chain. 72 | 73 | :raises RuntimeError: If the chain hash on the journal block is broken. 74 | """ 75 | if previous_journal_block is None: 76 | return journal_block 77 | if previous_journal_block.block_hash != journal_block.previous_block_hash: 78 | raise RuntimeError('Previous block hash does not match!') 79 | 80 | block_hash = join_hash_pairwise(journal_block.entries_hash, previous_journal_block.block_hash) 81 | if block_hash != journal_block.block_hash: 82 | raise RuntimeError("Block hash doesn't match expected block hash. Verification failed.") 83 | 84 | return journal_block 85 | 86 | 87 | def verify(journal_blocks): 88 | """ 89 | Validate that the chain hash on the journal block is valid. 90 | 91 | :type journal_blocks: list 92 | :param journal_blocks: A list of journal blocks. 93 | 94 | :return: None if the given list of journal blocks is empty. 95 | """ 96 | if len(journal_blocks) == 0: 97 | return 98 | reduce(compare_journal_blocks, journal_blocks) 99 | 100 | 101 | def main(ledger_name=Constants.LEDGER_NAME): 102 | """ 103 | Validate the hash chain of a QLDB ledger by stepping through its S3 export. 104 | 105 | This code accepts an exportID as an argument, if exportID is passed the code 106 | will use that or request QLDB to generate a new export to perform QLDB hash 107 | chain validation. 108 | """ 109 | s3_client = client('s3') 110 | try: 111 | if len(argv) == 2: 112 | export_id = argv[1] 113 | logger.info('Validating qldb hash chain for ExportId: {}.'.format(export_id)) 114 | 115 | else: 116 | logger.info('Requesting qldb to create an export.') 117 | export_id = create_journal_export() 118 | 119 | journal_export = describe_journal_export(ledger_name, export_id).get('ExportDescription') 120 | journal_blocks = read_export(journal_export, s3_client) 121 | verify(journal_blocks) 122 | except Exception as e: 123 | logger.exception('Unable to perform hash chain verification.') 124 | raise e 125 | 126 | 127 | if __name__ == '__main__': 128 | main() 129 | -------------------------------------------------------------------------------- /pyqldbsamples/verifier.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | # 17 | # This code expects that you have AWS credentials setup per: 18 | # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 19 | from array import array 20 | from base64 import b64encode 21 | from functools import reduce 22 | from hashlib import sha256 23 | from random import randrange 24 | 25 | from amazon.ion.simpleion import loads 26 | 27 | HASH_LENGTH = 32 28 | UPPER_BOUND = 8 29 | 30 | 31 | def parse_proof(value_holder): 32 | """ 33 | Parse the Proof object returned by QLDB into an iterator. 34 | 35 | The Proof object returned by QLDB is a dictionary like the following: 36 | {'IonText': '[{{}},{{}}]'} 37 | 38 | :type value_holder: dict 39 | :param value_holder: A structure containing an Ion string value. 40 | 41 | :rtype: :py:class:`amazon.ion.simple_types.IonPyList` 42 | :return: A list of hash values. 43 | """ 44 | value_holder = value_holder.get('IonText') 45 | proof_list = loads(value_holder) 46 | return proof_list 47 | 48 | 49 | def parse_block(value_holder): 50 | """ 51 | Parse the Block object returned by QLDB and retrieve block hash. 52 | 53 | :type value_holder: dict 54 | :param value_holder: A structure containing an Ion string value. 55 | 56 | :rtype: :py:class:`amazon.ion.simple_types.IonPyBytes` 57 | :return: The block hash. 58 | """ 59 | value_holder = value_holder.get('IonText') 60 | block = loads(value_holder) 61 | block_hash = block.get('blockHash') 62 | return block_hash 63 | 64 | 65 | def flip_random_bit(original): 66 | """ 67 | Flip a single random bit in the given hash value. 68 | This method is used to demonstrate QLDB's verification features. 69 | 70 | :type original: bytes 71 | :param original: The hash value to alter. 72 | 73 | :rtype: bytes 74 | :return: The altered hash with a single random bit changed. 75 | """ 76 | assert len(original) != 0, 'Invalid bytes.' 77 | 78 | altered_position = randrange(len(original)) 79 | bit_shift = randrange(UPPER_BOUND) 80 | altered_hash = bytearray(original).copy() 81 | 82 | altered_hash[altered_position] = altered_hash[altered_position] ^ (1 << bit_shift) 83 | return bytes(altered_hash) 84 | 85 | 86 | def compare_hash_values(hash1, hash2): 87 | """ 88 | Compare two hash values by converting them into byte arrays, assuming they are little endian. 89 | 90 | :type hash1: bytes 91 | :param hash1: The hash value to compare. 92 | 93 | :type hash2: bytes 94 | :param hash2: The hash value to compare. 95 | 96 | :rtype: int 97 | :return: Zero if the hash values are equal, otherwise return the difference of the first pair of non-matching bytes. 98 | """ 99 | assert len(hash1) == HASH_LENGTH 100 | assert len(hash2) == HASH_LENGTH 101 | 102 | hash_array1 = array('b', hash1) 103 | hash_array2 = array('b', hash2) 104 | 105 | for i in range(len(hash_array1) - 1, -1, -1): 106 | difference = hash_array1[i] - hash_array2[i] 107 | if difference != 0: 108 | return difference 109 | return 0 110 | 111 | 112 | def join_hash_pairwise(hash1, hash2): 113 | """ 114 | Take two hash values, sort them, concatenate them, and generate a new hash value from the concatenated values. 115 | 116 | :type hash1: bytes 117 | :param hash1: Hash value to concatenate. 118 | 119 | :type hash2: bytes 120 | :param hash2: Hash value to concatenate. 121 | 122 | :rtype: bytes 123 | :return: The new hash value generated from concatenated hash values. 124 | """ 125 | if len(hash1) == 0: 126 | return hash2 127 | if len(hash2) == 0: 128 | return hash1 129 | 130 | concatenated = hash1 + hash2 if compare_hash_values(hash1, hash2) < 0 else hash2 + hash1 131 | new_hash_lib = sha256() 132 | new_hash_lib.update(concatenated) 133 | new_digest = new_hash_lib.digest() 134 | return new_digest 135 | 136 | 137 | def calculate_root_hash_from_internal_hashes(internal_hashes, leaf_hash): 138 | """ 139 | Combine the internal hashes and the leaf hash until only one root hash remains. 140 | 141 | :type internal_hashes: map 142 | :param internal_hashes: An iterable over a list of hash values. 143 | 144 | :type leaf_hash: bytes 145 | :param leaf_hash: The revision hash to pair with the first hash in the Proof hashes list. 146 | 147 | :rtype: bytes 148 | :return: The root hash constructed by combining internal hashes. 149 | """ 150 | root_hash = reduce(join_hash_pairwise, internal_hashes, leaf_hash) 151 | return root_hash 152 | 153 | 154 | def build_candidate_digest(proof, leaf_hash): 155 | """ 156 | Build the candidate digest representing the entire ledger from the Proof hashes. 157 | 158 | :type proof: dict 159 | :param proof: The Proof object. 160 | 161 | :type leaf_hash: bytes 162 | :param leaf_hash: The revision hash to pair with the first hash in the Proof hashes list. 163 | 164 | :rtype: bytes 165 | :return: The calculated root hash. 166 | """ 167 | parsed_proof = parse_proof(proof) 168 | root_hash = calculate_root_hash_from_internal_hashes(parsed_proof, leaf_hash) 169 | return root_hash 170 | 171 | 172 | def verify_document(document_hash, digest, proof): 173 | """ 174 | Verify document revision against the provided digest. 175 | 176 | :type document_hash: bytes 177 | :param document_hash: The SHA-256 value representing the document revision to be verified. 178 | 179 | :type digest: bytes 180 | :param digest: The SHA-256 hash value representing the ledger digest. 181 | 182 | :type proof: dict 183 | :param proof: The Proof object retrieved from :func:`pyqldbsamples.get_revision.get_revision`. 184 | 185 | :rtype: bool 186 | :return: If the document revision verify against the ledger digest. 187 | """ 188 | candidate_digest = build_candidate_digest(proof, document_hash) 189 | return digest == candidate_digest 190 | 191 | 192 | def to_base_64(input): 193 | """ 194 | Encode input in base64. 195 | 196 | :type input: bytes 197 | :param input: Input to be encoded. 198 | 199 | :rtype: string 200 | :return: Return input that has been encoded in base64. 201 | """ 202 | encoded_value = b64encode(input) 203 | return str(encoded_value, 'UTF-8') 204 | -------------------------------------------------------------------------------- /requirements-docs.txt: -------------------------------------------------------------------------------- 1 | Sphinx>=2.2.0 2 | guzzle_sphinx_theme>=0.7.10,<0.8 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amazon.ion~=0.9.3 2 | boto3~=1.26.37 3 | botocore~=1.29.50 4 | pyqldb>=3.1.0,<4 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy of this 5 | # software and associated documentation files (the "Software"), to deal in the Software 6 | # without restriction, including without limitation the rights to use, copy, modify, 7 | # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | # permit persons to whom the Software is furnished to do so. 9 | # 10 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 11 | # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 12 | # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 13 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 14 | # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 15 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 16 | import os 17 | import re 18 | import setuptools 19 | 20 | ROOT = os.path.join(os.path.dirname(__file__), 'pyqldbsamples') 21 | VERSION_RE = re.compile(r'''__version__ = ['"]([0-9.a-z\-]+)['"]''') 22 | requires = ['amazon.ion>=0.7.0,<1', 23 | 'boto3>=1.16.56,<2', 24 | 'botocore>=1.19.56,<2', 25 | 'pyqldb>=3.1.0,<4' 26 | ] 27 | 28 | 29 | def get_version(): 30 | init = open(os.path.join(ROOT, '__init__.py')).read() 31 | return VERSION_RE.search(init).group(1) 32 | 33 | 34 | setuptools.setup( 35 | name='pyqldbsamples', 36 | version=get_version(), 37 | description='Sample app for Amazon QLDB', 38 | long_description=open('README.md').read(), 39 | long_description_content_type='text/markdown', 40 | author='Amazon Web Services', 41 | packages=setuptools.find_packages(), 42 | install_requires=requires, 43 | license="Apache License 2.0" 44 | ) 45 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | # the License. A copy of the License is located at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 10 | # and limitations under the License. 11 | -------------------------------------------------------------------------------- /tests/cleanup.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | # the License. A copy of the License is located at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 10 | # and limitations under the License. 11 | from time import time, sleep 12 | from sys import argv 13 | 14 | from boto3 import resource 15 | from pyqldb.driver.qldb_driver import QldbDriver 16 | 17 | from pyqldbsamples.delete_ledger import delete_ledger, set_deletion_protection, wait_for_deleted 18 | 19 | 20 | def poll_for_table_creation(ledger_name): 21 | driver = QldbDriver(ledger_name) 22 | max_poll_time = time() + 15 23 | while True: 24 | tables = driver.list_tables() 25 | count = len(list(tables)) 26 | if count == 4 or time() > max_poll_time: 27 | break 28 | sleep(3) 29 | 30 | 31 | def force_delete_ledger(ledger_name): 32 | try: 33 | set_deletion_protection(ledger_name, False) 34 | delete_ledger(ledger_name) 35 | wait_for_deleted(ledger_name) 36 | except Exception: 37 | pass 38 | 39 | 40 | def force_delete_s3_bucket(bucket_name): 41 | s3_resource = resource('s3') 42 | bucket = s3_resource.Bucket(bucket_name) 43 | 44 | try: 45 | for key in bucket.objects.all(): 46 | key.delete() 47 | bucket.delete() 48 | except Exception: 49 | pass 50 | 51 | 52 | def force_delete_role(role_name): 53 | iam = resource('iam') 54 | role = iam.Role(role_name) 55 | 56 | try: 57 | role.delete() 58 | except Exception: 59 | pass 60 | 61 | 62 | def force_delete_role_policies(role_name): 63 | iam_client = resource('iam') 64 | role = iam_client.Role(role_name) 65 | 66 | try: 67 | list_of_polices = list(role.attached_policies.all()) 68 | for policy in list_of_polices: 69 | policy.detach_role(RoleName=role_name) 70 | 71 | policy = iam_client.Policy(policy.arn) 72 | policy.delete() 73 | except Exception: 74 | pass 75 | 76 | 77 | def get_deletion_ledger_name(ledger_suffix): 78 | return 'py-dmv-' + ledger_suffix + '-delete' 79 | 80 | 81 | def get_ledger_name(ledger_suffix): 82 | return 'py-dmv-' + ledger_suffix 83 | 84 | 85 | def get_role_name(ledger_suffix): 86 | return 'github-actions-' + ledger_suffix + '-role' 87 | 88 | 89 | def get_role_policy_name(ledger_suffix): 90 | return 'github-actions-' + ledger_suffix + '-policy' 91 | 92 | 93 | def get_s3_bucket_name(ledger_suffix): 94 | return 'github-actions-' + ledger_suffix + '-bucket' 95 | 96 | 97 | def get_tag_ledger_name(ledger_suffix): 98 | return 'py-dmv-' + ledger_suffix + '-tags' 99 | 100 | 101 | def delete_resources(ledger_suffix): 102 | role_name = get_role_name(ledger_suffix) 103 | s3_bucket_name = get_s3_bucket_name(ledger_suffix) 104 | ledger_name = get_ledger_name(ledger_suffix) 105 | deletion_ledger_name = get_deletion_ledger_name(ledger_suffix) 106 | tag_ledger_name = get_tag_ledger_name(ledger_suffix) 107 | 108 | force_delete_role_policies(role_name) 109 | force_delete_role(role_name) 110 | force_delete_s3_bucket(s3_bucket_name) 111 | 112 | force_delete_ledger(ledger_name) 113 | force_delete_ledger(deletion_ledger_name) 114 | force_delete_ledger(tag_ledger_name) 115 | 116 | 117 | if __name__ == '__main__': 118 | if len(argv) > 1: 119 | ledger_suffix = argv[1] 120 | delete_resources(ledger_suffix) 121 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | # the License. A copy of the License is located at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 10 | # and limitations under the License. 11 | import pytest 12 | 13 | 14 | def pytest_addoption(parser): 15 | # Collect config values from cmd line or setup.cfg 16 | parser.addoption( 17 | "--ledger_suffix", action="store", default="", help="" 18 | ) 19 | 20 | 21 | @pytest.fixture(scope='class', autouse=True) 22 | def config_variables(request): 23 | # Set as class attribute on the invoking test context. 24 | request.cls.ledger_suffix = request.config.getoption("--ledger_suffix").replace(".", "-") 25 | -------------------------------------------------------------------------------- /tests/test_integration.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with 4 | # the License. A copy of the License is located at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 9 | # CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions 10 | # and limitations under the License. 11 | import sys 12 | from unittest import TestCase 13 | 14 | import pytest 15 | from pyqldbsamples.add_secondary_owner import main as add_secondary_owner_main 16 | from pyqldbsamples.connect_to_ledger import main as connect_to_ledger_main 17 | from pyqldbsamples.create_index import main as create_index_main 18 | from pyqldbsamples.create_ledger import main as create_ledger_main 19 | from pyqldbsamples.create_table import main as create_table_main 20 | from pyqldbsamples.deletion_protection import main as deletion_protection_main 21 | from pyqldbsamples.deregister_drivers_license import main as deregister_drivers_license_main 22 | from pyqldbsamples.describe_journal_export import main as describe_journal_export_main 23 | from pyqldbsamples.describe_ledger import main as describe_ledger_main 24 | from pyqldbsamples.export_journal import main as export_journal_main 25 | from pyqldbsamples.export_journal import create_export_role, set_up_s3_encryption_configuration 26 | from pyqldbsamples.find_vehicles import main as find_vehicles_main 27 | from pyqldbsamples.get_block import main as get_block_main 28 | from pyqldbsamples.get_digest import main as get_digest_main 29 | from pyqldbsamples.get_revision import main as get_revision_main 30 | from pyqldbsamples.insert_document import main as insert_document_main 31 | from pyqldbsamples.insert_ion_types import main as insert_ion_types_main 32 | from pyqldbsamples.list_journal_exports import main as list_journal_exports_main 33 | from pyqldbsamples.list_ledgers import main as list_ledgers_main 34 | from pyqldbsamples.list_tables import main as list_tables_main 35 | from pyqldbsamples.query_history import main as query_history_main 36 | from pyqldbsamples.redact_revision import main as redact_revision_main 37 | from pyqldbsamples.register_drivers_license import main as register_drivers_license_main 38 | from pyqldbsamples.renew_drivers_license import main as renew_drivers_license_main 39 | from pyqldbsamples.scan_table import main as scan_table_main 40 | from pyqldbsamples.tag_resource import main as tag_resource_main 41 | from pyqldbsamples.transfer_vehicle_ownership import main as transfer_vehicle_ownership_main 42 | from pyqldbsamples.validate_qldb_hash_chain import main as validate_qldb_hash_chain_main 43 | from tests.cleanup import get_deletion_ledger_name, get_ledger_name, get_role_name, get_role_policy_name, \ 44 | get_s3_bucket_name, get_tag_ledger_name, delete_resources, poll_for_table_creation 45 | 46 | 47 | # The following tests only run the samples. 48 | @pytest.mark.usefixtures("config_variables") 49 | class TestIntegration(TestCase): 50 | 51 | @classmethod 52 | def setUpClass(cls): 53 | cls.role_name = get_role_name(cls.ledger_suffix) 54 | cls.role_policy_name = get_role_policy_name(cls.ledger_suffix) 55 | cls.s3_bucket_name = get_s3_bucket_name(cls.ledger_suffix) 56 | cls.ledger_name = get_ledger_name(cls.ledger_suffix) 57 | cls.deletion_ledger_name = get_deletion_ledger_name(cls.ledger_suffix) 58 | cls.tag_ledger_name = get_tag_ledger_name(cls.ledger_suffix) 59 | 60 | delete_resources(cls.ledger_suffix) 61 | 62 | s3_encryption_config = set_up_s3_encryption_configuration() 63 | cls.role_arn = create_export_role(cls.role_name, s3_encryption_config.get('KmsKeyArn'), cls.role_policy_name, 64 | cls.s3_bucket_name) 65 | 66 | create_ledger_main(cls.ledger_name) 67 | create_table_main(cls.ledger_name) 68 | poll_for_table_creation(cls.ledger_name) 69 | create_index_main(cls.ledger_name) 70 | insert_document_main(cls.ledger_name) 71 | 72 | @classmethod 73 | def tearDownClass(cls): 74 | delete_resources(cls.ledger_suffix) 75 | 76 | def test_list_ledgers(self): 77 | list_ledgers_main() 78 | 79 | def test_connect_to_ledger(self): 80 | connect_to_ledger_main(self.ledger_name) 81 | 82 | def test_insert_ion_types(self): 83 | insert_ion_types_main(self.ledger_name) 84 | 85 | def test_scan_table(self): 86 | scan_table_main(self.ledger_name) 87 | 88 | def test_find_vehicles(self): 89 | find_vehicles_main(self.ledger_name) 90 | 91 | def test_add_secondary_owner(self): 92 | add_secondary_owner_main(self.ledger_name) 93 | 94 | def test_deregister_drivers_license(self): 95 | deregister_drivers_license_main(self.ledger_name) 96 | 97 | def test_export_journal_and_describe_journal_export_and_validate_qldb_hash(self): 98 | sys.argv[1:] = [self.s3_bucket_name, self.role_arn] 99 | export_id = export_journal_main(self.ledger_name).get('ExportId') 100 | 101 | sys.argv[1:] = [export_id] 102 | describe_journal_export_main(self.ledger_name) 103 | 104 | sys.argv[1:] = [export_id] 105 | validate_qldb_hash_chain_main(self.ledger_name) 106 | 107 | def test_describe_ledger(self): 108 | describe_ledger_main(self.ledger_name) 109 | 110 | def test_transfer_vehicle_ownership(self): 111 | transfer_vehicle_ownership_main(self.ledger_name) 112 | 113 | def test_query_history(self): 114 | query_history_main(self.ledger_name) 115 | 116 | def test_redact_revision(self): 117 | redact_revision_main(self.ledger_name) 118 | 119 | def test_list_tables(self): 120 | list_tables_main(self.ledger_name) 121 | 122 | def test_register_drivers_license(self): 123 | register_drivers_license_main(self.ledger_name) 124 | 125 | def test_renew_drivers_license(self): 126 | renew_drivers_license_main(self.ledger_name) 127 | 128 | def test_deletion_protection(self): 129 | deletion_protection_main(self.deletion_ledger_name) 130 | 131 | def test_list_journal_exports(self): 132 | list_journal_exports_main(self.ledger_name) 133 | 134 | def test_get_revision(self): 135 | get_revision_main(self.ledger_name) 136 | 137 | def test_get_block(self): 138 | get_block_main(self.ledger_name) 139 | 140 | def test_get_digest(self): 141 | get_digest_main(self.ledger_name) 142 | 143 | def test_tag_resource(self): 144 | tag_resource_main(self.tag_ledger_name) 145 | --------------------------------------------------------------------------------