├── .editorconfig ├── .flake8 ├── .github ├── .stale.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── build.yml │ ├── greetings.yml │ └── release-drafter.yml ├── .gitignore ├── .isort.cfg ├── .pre-commit-config.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── assets └── images │ ├── coverage.svg │ └── pydastic.png ├── poetry.lock ├── pydastic ├── __init__.py ├── error.py ├── model.py ├── pydastic.py └── session.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── tests ├── conftest.py ├── test_model.py ├── test_session.py └── user.py └── todo_checker.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | # Check http://editorconfig.org for more information 2 | # This is the main config file for this project: 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | indent_style = space 10 | indent_size = 2 11 | trim_trailing_whitespace = true 12 | 13 | [*.{py, pyi}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [Makefile] 18 | indent_style = tab 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | [*.{diff,patch}] 24 | trim_trailing_whitespace = false 25 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | # Autoformatter friendly flake8 config (all formatting rules disabled) 2 | [flake8] 3 | extend-ignore = E1, E2, E3, E501, W1, W2, W3, W5 4 | -------------------------------------------------------------------------------- /.github/.stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐛 Bug report 3 | about: If something isn't working 🔧 4 | title: '' 5 | labels: bug 6 | assignees: 7 | --- 8 | 9 | ## 🐛 Bug Report 10 | 11 | 12 | 13 | ## 🔬 How To Reproduce 14 | 15 | Steps to reproduce the behavior: 16 | 17 | 1. ... 18 | 19 | ### Code sample 20 | 21 | 22 | 23 | ### Environment 24 | 25 | * OS: [e.g. Linux / Windows / macOS] 26 | * Python version, get it with: 27 | 28 | ```bash 29 | python --version 30 | ``` 31 | 32 | ### Screenshots 33 | 34 | 35 | 36 | ## 📈 Expected behavior 37 | 38 | 39 | 40 | ## 📎 Additional context 41 | 42 | 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration: https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository 2 | 3 | blank_issues_enabled: false 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🚀 Feature request 3 | about: Suggest an idea for this project 🏖 4 | title: '' 5 | labels: enhancement 6 | assignees: 7 | --- 8 | 9 | ## 🚀 Feature Request 10 | 11 | 12 | 13 | ## 🔈 Motivation 14 | 15 | 16 | 17 | ## 🛰 Alternatives 18 | 19 | 20 | 21 | ## 📎 Additional context 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: ❓ Question 3 | about: Ask a question about this project 🎓 4 | title: '' 5 | labels: question 6 | assignees: 7 | --- 8 | 9 | ## Checklist 10 | 11 | 12 | 13 | - [ ] I've searched the project's [`issues`](https://github.com/ramiawar/pydastic/issues?q=is%3Aissue). 14 | 15 | ## ❓ Question 16 | 17 | 18 | 19 | How can I [...]? 20 | 21 | Is it possible to [...]? 22 | 23 | ## 📎 Additional context 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | 5 | ## Related Issue 6 | 7 | 8 | 9 | ## Type of Change 10 | 11 | 12 | 13 | - [ ] 📚 Examples / docs / tutorials / dependencies update 14 | - [ ] 🔧 Bug fix (non-breaking change which fixes an issue) 15 | - [ ] 🥂 Improvement (non-breaking change which improves an existing feature) 16 | - [ ] 🚀 New feature (non-breaking change which adds functionality) 17 | - [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change) 18 | - [ ] 🔐 Security fix 19 | 20 | ## Checklist 21 | 22 | 23 | 24 | - [ ] I've read the [`CODE_OF_CONDUCT.md`](https://github.com/pydastic/pydastic/blob/master/CODE_OF_CONDUCT.md) document. 25 | - [ ] I've read the [`CONTRIBUTING.md`](https://github.com/pydastic/pydastic/blob/master/CONTRIBUTING.md) guide. 26 | - [ ] I've updated the code style using `make codestyle`. 27 | - [ ] I've written tests for all new methods and classes that I created. 28 | - [ ] I've written the docstring in Google format for all the methods and classes that I used. 29 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Configuration: https://dependabot.com/docs/config-file/ 2 | # Docs: https://docs.github.com/en/github/administering-a-repository/keeping-your-dependencies-updated-automatically 3 | 4 | version: 2 5 | 6 | updates: 7 | - package-ecosystem: "pip" 8 | directory: "/" 9 | schedule: 10 | interval: "daily" 11 | allow: 12 | - dependency-type: "all" 13 | commit-message: 14 | prefix: ":arrow_up:" 15 | open-pull-requests-limit: 50 16 | 17 | - package-ecosystem: "github-actions" 18 | directory: "/" 19 | schedule: 20 | interval: "daily" 21 | allow: 22 | - dependency-type: "all" 23 | commit-message: 24 | prefix: ":arrow_up:" 25 | open-pull-requests-limit: 50 26 | 27 | - package-ecosystem: "docker" 28 | directory: "/docker" 29 | schedule: 30 | interval: "weekly" 31 | allow: 32 | - dependency-type: "all" 33 | commit-message: 34 | prefix: ":arrow_up:" 35 | open-pull-requests-limit: 50 36 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Release drafter configuration https://github.com/release-drafter/release-drafter#configuration 2 | # Emojis were chosen to match the https://gitmoji.carloscuesta.me/ 3 | 4 | name-template: "v$NEXT_PATCH_VERSION" 5 | tag-template: "v$NEXT_PATCH_VERSION" 6 | 7 | categories: 8 | - title: ":rocket: Features" 9 | labels: [enhancement, feature] 10 | - title: ":wrench: Fixes & Refactoring" 11 | labels: [bug, refactoring, bugfix, fix] 12 | - title: ":package: Build System & CI/CD" 13 | labels: [build, ci, testing] 14 | - title: ":boom: Breaking Changes" 15 | labels: [breaking] 16 | - title: ":pencil: Documentation" 17 | labels: [documentation] 18 | - title: ":arrow_up: Dependencies updates" 19 | labels: [dependencies] 20 | 21 | template: | 22 | ## What’s Changed 23 | 24 | $CHANGES 25 | 26 | ## :busts_in_silhouette: List of contributors 27 | 28 | $CONTRIBUTORS 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.7"] 11 | es-version: ["7.12.0", "8.1.2"] 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v3.1.2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | 20 | - name: Configure sysctl limits 21 | run: | 22 | sudo swapoff -a 23 | sudo sysctl -w vm.swappiness=1 24 | sudo sysctl -w fs.file-max=262144 25 | sudo sysctl -w vm.max_map_count=262144 26 | 27 | - name: Runs Elasticsearch 28 | uses: elastic/elastic-github-actions/elasticsearch@master 29 | with: 30 | stack-version: ${{ matrix.es-version }} 31 | 32 | - name: Install poetry 33 | run: make poetry-download 34 | 35 | - name: Set up cache 36 | uses: actions/cache@v3.0.2 37 | with: 38 | path: .venv 39 | key: venv-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}-${{ hashFiles('poetry.lock') }} 40 | 41 | - name: Install dependencies 42 | run: | 43 | poetry config virtualenvs.in-project true 44 | poetry install 45 | 46 | - name: Upgrade es client 47 | run: poetry add elasticsearch=${{ matrix.es-version }} 48 | 49 | - name: Run style checks 50 | run: | 51 | make check-codestyle 52 | 53 | - name: Run tests 54 | run: | 55 | make test 56 | 57 | - name: Run safety checks 58 | run: | 59 | make check-safety 60 | -------------------------------------------------------------------------------- /.github/workflows/greetings.yml: -------------------------------------------------------------------------------- 1 | name: Greetings 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | pr-message: 'Hello @${{ github.actor }}, thank you for submitting a PR! We will respond as soon as possible.' 13 | issue-message: | 14 | Hello @${{ github.actor }}, thank you for your interest in our work! 15 | 16 | If this is a bug report, please provide screenshots and **minimum viable code to reproduce your issue**, otherwise we can not help you. 17 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | # Drafts your next Release notes as Pull Requests are merged into "master" 14 | - uses: release-drafter/release-drafter@v5.19.0 15 | env: 16 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,python,pycharm,windows,visualstudio,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=osx,python,pycharm,windows,visualstudio,visualstudiocode 4 | 5 | ### OSX ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### PyCharm ### 34 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 35 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 36 | 37 | # User-specific stuff 38 | .idea/**/workspace.xml 39 | .idea/**/tasks.xml 40 | .idea/**/usage.statistics.xml 41 | .idea/**/dictionaries 42 | .idea/**/shelf 43 | 44 | # Generated files 45 | .idea/**/contentModel.xml 46 | 47 | # Sensitive or high-churn files 48 | .idea/**/dataSources/ 49 | .idea/**/dataSources.ids 50 | .idea/**/dataSources.local.xml 51 | .idea/**/sqlDataSources.xml 52 | .idea/**/dynamic.xml 53 | .idea/**/uiDesigner.xml 54 | .idea/**/dbnavigator.xml 55 | 56 | # Gradle 57 | .idea/**/gradle.xml 58 | .idea/**/libraries 59 | 60 | # Gradle and Maven with auto-import 61 | # When using Gradle or Maven with auto-import, you should exclude module files, 62 | # since they will be recreated, and may cause churn. Uncomment if using 63 | # auto-import. 64 | # .idea/modules.xml 65 | # .idea/*.iml 66 | # .idea/modules 67 | # *.iml 68 | # *.ipr 69 | 70 | # CMake 71 | cmake-build-*/ 72 | 73 | # Mongo Explorer plugin 74 | .idea/**/mongoSettings.xml 75 | 76 | # File-based project format 77 | *.iws 78 | 79 | # IntelliJ 80 | out/ 81 | 82 | # mpeltonen/sbt-idea plugin 83 | .idea_modules/ 84 | 85 | # JIRA plugin 86 | atlassian-ide-plugin.xml 87 | 88 | # Cursive Clojure plugin 89 | .idea/replstate.xml 90 | 91 | # Crashlytics plugin (for Android Studio and IntelliJ) 92 | com_crashlytics_export_strings.xml 93 | crashlytics.properties 94 | crashlytics-build.properties 95 | fabric.properties 96 | 97 | # Editor-based Rest Client 98 | .idea/httpRequests 99 | 100 | # Android studio 3.1+ serialized cache file 101 | .idea/caches/build_file_checksums.ser 102 | 103 | ### PyCharm Patch ### 104 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 105 | 106 | # *.iml 107 | # modules.xml 108 | # .idea/misc.xml 109 | # *.ipr 110 | 111 | # Sonarlint plugin 112 | .idea/**/sonarlint/ 113 | 114 | # SonarQube Plugin 115 | .idea/**/sonarIssues.xml 116 | 117 | # Markdown Navigator plugin 118 | .idea/**/markdown-navigator.xml 119 | .idea/**/markdown-navigator/ 120 | 121 | ### Python ### 122 | # Byte-compiled / optimized / DLL files 123 | __pycache__/ 124 | *.py[cod] 125 | *$py.class 126 | 127 | # C extensions 128 | *.so 129 | 130 | # Distribution / packaging 131 | .Python 132 | build/ 133 | develop-eggs/ 134 | dist/ 135 | downloads/ 136 | eggs/ 137 | .eggs/ 138 | lib/ 139 | lib64/ 140 | parts/ 141 | sdist/ 142 | var/ 143 | wheels/ 144 | pip-wheel-metadata/ 145 | share/python-wheels/ 146 | *.egg-info/ 147 | .installed.cfg 148 | *.egg 149 | MANIFEST 150 | 151 | # PyInstaller 152 | # Usually these files are written by a python script from a template 153 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 154 | *.manifest 155 | *.spec 156 | 157 | # Installer logs 158 | pip-log.txt 159 | pip-delete-this-directory.txt 160 | 161 | # Unit test / coverage reports 162 | htmlcov/ 163 | .tox/ 164 | .nox/ 165 | .coverage 166 | .coverage.* 167 | .cache 168 | nosetests.xml 169 | coverage.xml 170 | *.cover 171 | .hypothesis/ 172 | .pytest_cache/ 173 | 174 | # Translations 175 | *.mo 176 | *.pot 177 | 178 | # Scrapy stuff: 179 | .scrapy 180 | 181 | # Sphinx documentation 182 | docs/_build/ 183 | 184 | # PyBuilder 185 | target/ 186 | 187 | # pyenv 188 | .python-version 189 | 190 | # poetry 191 | .venv 192 | 193 | # pipenv 194 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 195 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 196 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 197 | # install all needed dependencies. 198 | #Pipfile.lock 199 | 200 | # celery beat schedule file 201 | celerybeat-schedule 202 | 203 | # SageMath parsed files 204 | *.sage.py 205 | 206 | # Spyder project settings 207 | .spyderproject 208 | .spyproject 209 | 210 | # Rope project settings 211 | .ropeproject 212 | 213 | # Mr Developer 214 | .mr.developer.cfg 215 | .project 216 | .pydevproject 217 | 218 | # mkdocs documentation 219 | /site 220 | 221 | # mypy 222 | .mypy_cache/ 223 | .dmypy.json 224 | dmypy.json 225 | 226 | # Pyre type checker 227 | .pyre/ 228 | 229 | # Plugins 230 | .secrets.baseline 231 | 232 | ### VisualStudioCode ### 233 | .vscode/* 234 | !.vscode/tasks.json 235 | !.vscode/launch.json 236 | !.vscode/extensions.json 237 | 238 | ### VisualStudioCode Patch ### 239 | # Ignore all local history of files 240 | .history 241 | 242 | ### Windows ### 243 | # Windows thumbnail cache files 244 | Thumbs.db 245 | Thumbs.db:encryptable 246 | ehthumbs.db 247 | ehthumbs_vista.db 248 | 249 | # Dump file 250 | *.stackdump 251 | 252 | # Folder config file 253 | [Dd]esktop.ini 254 | 255 | # Recycle Bin used on file shares 256 | $RECYCLE.BIN/ 257 | 258 | # Windows Installer files 259 | *.cab 260 | *.msi 261 | *.msix 262 | *.msm 263 | *.msp 264 | 265 | # Windows shortcuts 266 | *.lnk 267 | 268 | ### VisualStudio ### 269 | ## Ignore Visual Studio temporary files, build results, and 270 | ## files generated by popular Visual Studio add-ons. 271 | ## 272 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 273 | 274 | # User-specific files 275 | *.rsuser 276 | *.suo 277 | *.user 278 | *.userosscache 279 | *.sln.docstates 280 | 281 | # User-specific files (MonoDevelop/Xamarin Studio) 282 | *.userprefs 283 | 284 | # Mono auto generated files 285 | mono_crash.* 286 | 287 | # Build results 288 | [Dd]ebug/ 289 | [Dd]ebugPublic/ 290 | [Rr]elease/ 291 | [Rr]eleases/ 292 | x64/ 293 | x86/ 294 | [Aa][Rr][Mm]/ 295 | [Aa][Rr][Mm]64/ 296 | bld/ 297 | [Bb]in/ 298 | [Oo]bj/ 299 | [Ll]og/ 300 | 301 | # Visual Studio 2015/2017 cache/options directory 302 | .vs/ 303 | # Uncomment if you have tasks that create the project's static files in wwwroot 304 | #wwwroot/ 305 | 306 | # Visual Studio 2017 auto generated files 307 | Generated\ Files/ 308 | 309 | # MSTest test Results 310 | [Tt]est[Rr]esult*/ 311 | [Bb]uild[Ll]og.* 312 | 313 | # NUnit 314 | *.VisualState.xml 315 | TestResult.xml 316 | nunit-*.xml 317 | 318 | # Build Results of an ATL Project 319 | [Dd]ebugPS/ 320 | [Rr]eleasePS/ 321 | dlldata.c 322 | 323 | # Benchmark Results 324 | BenchmarkDotNet.Artifacts/ 325 | 326 | # .NET Core 327 | project.lock.json 328 | project.fragment.lock.json 329 | artifacts/ 330 | 331 | # StyleCop 332 | StyleCopReport.xml 333 | 334 | # Files built by Visual Studio 335 | *_i.c 336 | *_p.c 337 | *_h.h 338 | *.ilk 339 | *.obj 340 | *.iobj 341 | *.pch 342 | *.pdb 343 | *.ipdb 344 | *.pgc 345 | *.pgd 346 | *.rsp 347 | *.sbr 348 | *.tlb 349 | *.tli 350 | *.tlh 351 | *.tmp 352 | *.tmp_proj 353 | *_wpftmp.csproj 354 | *.log 355 | *.vspscc 356 | *.vssscc 357 | .builds 358 | *.pidb 359 | *.svclog 360 | *.scc 361 | 362 | # Chutzpah Test files 363 | _Chutzpah* 364 | 365 | # Visual C++ cache files 366 | ipch/ 367 | *.aps 368 | *.ncb 369 | *.opendb 370 | *.opensdf 371 | *.sdf 372 | *.cachefile 373 | *.VC.db 374 | *.VC.VC.opendb 375 | 376 | # Visual Studio profiler 377 | *.psess 378 | *.vsp 379 | *.vspx 380 | *.sap 381 | 382 | # Visual Studio Trace Files 383 | *.e2e 384 | 385 | # TFS 2012 Local Workspace 386 | $tf/ 387 | 388 | # Guidance Automation Toolkit 389 | *.gpState 390 | 391 | # ReSharper is a .NET coding add-in 392 | _ReSharper*/ 393 | *.[Rr]e[Ss]harper 394 | *.DotSettings.user 395 | 396 | # JustCode is a .NET coding add-in 397 | .JustCode 398 | 399 | # TeamCity is a build add-in 400 | _TeamCity* 401 | 402 | # DotCover is a Code Coverage Tool 403 | *.dotCover 404 | 405 | # AxoCover is a Code Coverage Tool 406 | .axoCover/* 407 | !.axoCover/settings.json 408 | 409 | # Visual Studio code coverage results 410 | *.coverage 411 | *.coveragexml 412 | 413 | # NCrunch 414 | _NCrunch_* 415 | .*crunch*.local.xml 416 | nCrunchTemp_* 417 | 418 | # MightyMoose 419 | *.mm.* 420 | AutoTest.Net/ 421 | 422 | # Web workbench (sass) 423 | .sass-cache/ 424 | 425 | # Installshield output folder 426 | [Ee]xpress/ 427 | 428 | # DocProject is a documentation generator add-in 429 | DocProject/buildhelp/ 430 | DocProject/Help/*.HxT 431 | DocProject/Help/*.HxC 432 | DocProject/Help/*.hhc 433 | DocProject/Help/*.hhk 434 | DocProject/Help/*.hhp 435 | DocProject/Help/Html2 436 | DocProject/Help/html 437 | 438 | # Click-Once directory 439 | publish/ 440 | 441 | # Publish Web Output 442 | *.[Pp]ublish.xml 443 | *.azurePubxml 444 | # Note: Comment the next line if you want to checkin your web deploy settings, 445 | # but database connection strings (with potential passwords) will be unencrypted 446 | *.pubxml 447 | *.publishproj 448 | 449 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 450 | # checkin your Azure Web App publish settings, but sensitive information contained 451 | # in these scripts will be unencrypted 452 | PublishScripts/ 453 | 454 | # NuGet Packages 455 | *.nupkg 456 | # NuGet Symbol Packages 457 | *.snupkg 458 | # The packages folder can be ignored because of Package Restore 459 | **/[Pp]ackages/* 460 | # except build/, which is used as an MSBuild target. 461 | !**/[Pp]ackages/build/ 462 | # Uncomment if necessary however generally it will be regenerated when needed 463 | #!**/[Pp]ackages/repositories.config 464 | # NuGet v3's project.json files produces more ignorable files 465 | *.nuget.props 466 | *.nuget.targets 467 | 468 | # Microsoft Azure Build Output 469 | csx/ 470 | *.build.csdef 471 | 472 | # Microsoft Azure Emulator 473 | ecf/ 474 | rcf/ 475 | 476 | # Windows Store app package directories and files 477 | AppPackages/ 478 | BundleArtifacts/ 479 | Package.StoreAssociation.xml 480 | _pkginfo.txt 481 | *.appx 482 | *.appxbundle 483 | *.appxupload 484 | 485 | # Visual Studio cache files 486 | # files ending in .cache can be ignored 487 | *.[Cc]ache 488 | # but keep track of directories ending in .cache 489 | !?*.[Cc]ache/ 490 | 491 | # Others 492 | ClientBin/ 493 | ~$* 494 | *~ 495 | *.dbmdl 496 | *.dbproj.schemaview 497 | *.jfm 498 | *.pfx 499 | *.publishsettings 500 | orleans.codegen.cs 501 | 502 | # Including strong name files can present a security risk 503 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 504 | #*.snk 505 | 506 | # Since there are multiple workflows, uncomment next line to ignore bower_components 507 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 508 | #bower_components/ 509 | 510 | # RIA/Silverlight projects 511 | Generated_Code/ 512 | 513 | # Backup & report files from converting an old project file 514 | # to a newer Visual Studio version. Backup files are not needed, 515 | # because we have git ;-) 516 | _UpgradeReport_Files/ 517 | Backup*/ 518 | UpgradeLog*.XML 519 | UpgradeLog*.htm 520 | ServiceFabricBackup/ 521 | *.rptproj.bak 522 | 523 | # SQL Server files 524 | *.mdf 525 | *.ldf 526 | *.ndf 527 | 528 | # Business Intelligence projects 529 | *.rdl.data 530 | *.bim.layout 531 | *.bim_*.settings 532 | *.rptproj.rsuser 533 | *- [Bb]ackup.rdl 534 | *- [Bb]ackup ([0-9]).rdl 535 | *- [Bb]ackup ([0-9][0-9]).rdl 536 | 537 | # Microsoft Fakes 538 | FakesAssemblies/ 539 | 540 | # GhostDoc plugin setting file 541 | *.GhostDoc.xml 542 | 543 | # Node.js Tools for Visual Studio 544 | .ntvs_analysis.dat 545 | node_modules/ 546 | 547 | # Visual Studio 6 build log 548 | *.plg 549 | 550 | # Visual Studio 6 workspace options file 551 | *.opt 552 | 553 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 554 | *.vbw 555 | 556 | # Visual Studio LightSwitch build output 557 | **/*.HTMLClient/GeneratedArtifacts 558 | **/*.DesktopClient/GeneratedArtifacts 559 | **/*.DesktopClient/ModelManifest.xml 560 | **/*.Server/GeneratedArtifacts 561 | **/*.Server/ModelManifest.xml 562 | _Pvt_Extensions 563 | 564 | # Paket dependency manager 565 | .paket/paket.exe 566 | paket-files/ 567 | 568 | # FAKE - F# Make 569 | .fake/ 570 | 571 | # CodeRush personal settings 572 | .cr/personal 573 | 574 | # Python Tools for Visual Studio (PTVS) 575 | *.pyc 576 | 577 | # Cake - Uncomment if you are using it 578 | # tools/** 579 | # !tools/packages.config 580 | 581 | # Tabs Studio 582 | *.tss 583 | 584 | # Telerik's JustMock configuration file 585 | *.jmconfig 586 | 587 | # BizTalk build output 588 | *.btp.cs 589 | *.btm.cs 590 | *.odx.cs 591 | *.xsd.cs 592 | 593 | # OpenCover UI analysis results 594 | OpenCover/ 595 | 596 | # Azure Stream Analytics local run output 597 | ASALocalRun/ 598 | 599 | # MSBuild Binary and Structured Log 600 | *.binlog 601 | 602 | # NVidia Nsight GPU debugger configuration file 603 | *.nvuser 604 | 605 | # MFractors (Xamarin productivity tool) working folder 606 | .mfractor/ 607 | 608 | # Local History for Visual Studio 609 | .localhistory/ 610 | 611 | # BeatPulse healthcheck temp database 612 | healthchecksdb 613 | 614 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 615 | MigrationBackup/ 616 | 617 | # End of https://www.gitignore.io/api/osx,python,pycharm,windows,visualstudio,visualstudiocode 618 | -------------------------------------------------------------------------------- /.isort.cfg: -------------------------------------------------------------------------------- 1 | [settings] 2 | profile=black 3 | include_trailing_comma = true 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_language_version: 2 | python: python3.8 3 | 4 | default_stages: [commit, push] 5 | 6 | repos: 7 | - repo: https://github.com/pre-commit/pre-commit-hooks 8 | rev: v2.5.0 9 | hooks: 10 | - id: check-yaml 11 | - id: end-of-file-fixer 12 | exclude: LICENSE 13 | 14 | - repo: local 15 | hooks: 16 | - id: stylefix 17 | name: stylefix 18 | entry: | 19 | make codestyle 20 | language: system 21 | 22 | - repo: local 23 | hooks: 24 | - id: stylechecks 25 | name: stylechecks 26 | entry: | 27 | make check-codestyle 28 | language: system 29 | 30 | - repo: local 31 | hooks: 32 | - id: todo-checker 33 | name: todo-checker 34 | entry: todo_checker.sh 35 | language: script 36 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at rami.awar.ra@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | ## Dependencies 4 | 5 | We use `poetry` to manage the [dependencies](https://github.com/python-poetry/poetry). 6 | If you dont have `poetry`, you should install with `make poetry-download`. 7 | 8 | To install dependencies and prepare [`pre-commit`](https://pre-commit.com/) hooks you would need to run `install` command: 9 | 10 | ```bash 11 | make install 12 | make pre-commit-install 13 | ``` 14 | 15 | To activate your `virtualenv` run `poetry shell`. 16 | 17 | ## Codestyle 18 | 19 | After installation you may execute code formatting. 20 | 21 | ```bash 22 | make codestyle 23 | ``` 24 | 25 | ### Checks 26 | 27 | Many checks are configured for this project. Command `make check-codestyle` will check black, isort and darglint. 28 | The `make check-safety` command will look at the security of your code. 29 | 30 | Comand `make lint` applies all checks. 31 | 32 | ### Before submitting 33 | 34 | Before submitting your code please do the following steps: 35 | 36 | 1. Add any changes you want 37 | 1. Add tests for the new changes 38 | 1. Edit documentation if you have changed something significant 39 | 1. Run `make codestyle` to format your changes. 40 | 1. Run `make lint` to ensure that types, security and docstrings are okay. 41 | 42 | ## Other help 43 | 44 | You can contribute by spreading a word about this library. 45 | It would also be a huge contribution to write 46 | a short article on how you are using this project. 47 | You can also share your best practices with us. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2022 pydastic 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 20 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #* Variables 2 | SHELL := /usr/bin/env bash 3 | PYTHON := python 4 | PYTHONPATH := `pwd` 5 | 6 | #* Docker variables 7 | IMAGE := pydastic 8 | VERSION := latest 9 | 10 | #* Poetry 11 | .PHONY: poetry-download 12 | poetry-download: 13 | curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | $(PYTHON) - 14 | 15 | .PHONY: poetry-remove 16 | poetry-remove: 17 | curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py | $(PYTHON) - --uninstall 18 | 19 | #* Installation 20 | .PHONY: install 21 | install: 22 | poetry lock -n && poetry export --without-hashes > requirements.txt 23 | poetry install -n 24 | -poetry run mypy --install-types --non-interactive ./ 25 | 26 | .PHONY: pre-commit-install 27 | pre-commit-install: 28 | poetry run pre-commit install 29 | 30 | #* Formatters 31 | .PHONY: codestyle 32 | codestyle: 33 | poetry run pyupgrade --exit-zero-even-if-changed --py37-plus **/*.py 34 | poetry run isort --settings-path pyproject.toml ./ 35 | poetry run black --config pyproject.toml ./ 36 | 37 | .PHONY: formatting 38 | formatting: codestyle 39 | 40 | #* Linting 41 | .PHONY: test 42 | test: 43 | PYTHONPATH=$(PYTHONPATH) poetry run pytest -c pyproject.toml --cov-report html --cov-report xml --cov=pydastic tests/ 44 | poetry run coverage-badge -o assets/images/coverage.svg -f 45 | 46 | .PHONY: check-codestyle 47 | check-codestyle: 48 | poetry run isort --diff --check-only --settings-path pyproject.toml ./ 49 | poetry run black --diff --check --config pyproject.toml ./ 50 | poetry run darglint --verbosity 2 pydastic tests 51 | 52 | .PHONY: mypy 53 | mypy: 54 | poetry run mypy --config-file pyproject.toml ./ 55 | 56 | .PHONY: check-safety 57 | check-safety: 58 | poetry check 59 | poetry run safety check --full-report 60 | poetry run bandit -ll --recursive pydastic tests 61 | 62 | .PHONY: lint 63 | lint: test check-codestyle mypy check-safety 64 | 65 | .PHONY: update-dev-deps 66 | update-dev-deps: 67 | poetry add -D bandit@latest darglint@latest "isort[colors]@latest" mypy@latest pre-commit@latest pydocstyle@latest pylint@latest pytest@latest pyupgrade@latest safety@latest coverage@latest coverage-badge@latest pytest-html@latest pytest-cov@latest 68 | poetry add -D --allow-prereleases black@latest 69 | 70 | #* Docker 71 | # Example: make docker-build VERSION=latest 72 | # Example: make docker-build IMAGE=some_name VERSION=0.1.0 73 | .PHONY: docker-build 74 | docker-build: 75 | @echo Building docker $(IMAGE):$(VERSION) ... 76 | docker build \ 77 | -t $(IMAGE):$(VERSION) . \ 78 | -f ./docker/Dockerfile --no-cache 79 | 80 | # Example: make docker-remove VERSION=latest 81 | # Example: make docker-remove IMAGE=some_name VERSION=0.1.0 82 | .PHONY: docker-remove 83 | docker-remove: 84 | @echo Removing docker $(IMAGE):$(VERSION) ... 85 | docker rmi -f $(IMAGE):$(VERSION) 86 | 87 | #* Cleaning 88 | .PHONY: pycache-remove 89 | pycache-remove: 90 | find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf 91 | 92 | .PHONY: dsstore-remove 93 | dsstore-remove: 94 | find . | grep -E ".DS_Store" | xargs rm -rf 95 | 96 | .PHONY: mypycache-remove 97 | mypycache-remove: 98 | find . | grep -E ".mypy_cache" | xargs rm -rf 99 | 100 | .PHONY: ipynbcheckpoints-remove 101 | ipynbcheckpoints-remove: 102 | find . | grep -E ".ipynb_checkpoints" | xargs rm -rf 103 | 104 | .PHONY: pytestcache-remove 105 | pytestcache-remove: 106 | find . | grep -E ".pytest_cache" | xargs rm -rf 107 | 108 | .PHONY: build-remove 109 | build-remove: 110 | rm -rf build/ 111 | 112 | .PHONY: cleanup 113 | cleanup: pycache-remove dsstore-remove mypycache-remove ipynbcheckpoints-remove pytestcache-remove 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Pydastic

5 | 6 | 7 | Package version 8 | 9 | 10 | [![build](https://github.com/RamiAwar/pydastic/actions/workflows/build.yml/badge.svg)](https://github.com/RamiAwar/pydastic/actions/workflows/build.yml) 11 | [![Python Version](https://img.shields.io/pypi/pyversions/pydastic.svg)](https://pypi.org/project/pydastic/) 12 | [![Dependencies Status](https://img.shields.io/badge/dependencies-up%20to%20date-brightgreen.svg)](https://github.com/ramiawar/pydastic/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Aapp%2Fdependabot) 13 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 14 | 15 | [![Security: bandit](https://img.shields.io/badge/security-bandit-green.svg)](https://github.com/PyCQA/bandit) 16 | [![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/ramiawar/pydastic/blob/master/.pre-commit-config.yaml) 17 | [![Semantic Versions](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--versions-e10079.svg)](https://github.com/ramiawar/pydastic/releases) 18 | [![License](https://img.shields.io/github/license/ramiawar/pydastic)](https://github.com/ramiawar/pydastic/blob/master/LICENSE) 19 | ![Coverage Report](https://github.com/RamiAwar/pydastic/raw/main/assets/images/coverage.svg) 20 | 21 | Pydastic is an elasticsearch python ORM based on Pydantic. 22 | 23 |
24 | 25 | ## 💾 Installation 26 | 27 | Pip: 28 | ```bash 29 | pip install pydastic 30 | ``` 31 | 32 | Poetry: 33 | ```bash 34 | poetry add pydastic 35 | ``` 36 | 37 | 38 | ## 🚀 Core Features 39 | - Simple CRUD operations supported 40 | - Sessions for simplifying bulk operations (a la SQLAlchemy) 41 | - Dynamic index support when committing operations 42 | 43 | ## In Development 44 | - Search API (for now, exposed client can be used) 45 | 46 | 47 | ## 📋 Usage 48 | 49 | ### Defining Models 50 | ```python 51 | from pydastic import ESModel 52 | 53 | class User(ESModel): 54 | name: str 55 | phone: Optional[str] 56 | last_login: datetime = Field(default_factory=datetime.now) 57 | 58 | class Meta: 59 | index = "user" 60 | ``` 61 | 62 | ### Establishing Connection 63 | An elasticsearch connection can be setup by using the `connect` function. This function adopts the same signature as the `elasticsearch.Elasticsearch` client and supports editor autocomplete. 64 | Make sure to call this only once. No protection is put in place against multiple calls, might affect performance negatively. 65 | 66 | ```python 67 | from pydastic import connect 68 | 69 | connect(hosts="localhost:9200") 70 | ``` 71 | 72 | ### CRUD: Create, Update 73 | ```python 74 | # Create and save doc 75 | user = User(name="John", age=20) 76 | user.save(wait_for=True) # wait_for explained below 77 | 78 | assert user.id != None 79 | 80 | # Update doc 81 | user.name = "Sam" 82 | user.save(wait_for=True) 83 | ``` 84 | 85 | ### CRUD: Read Document 86 | ```python 87 | got = User.get(id=user.id) 88 | assert got == user 89 | ``` 90 | 91 | ### CRUD: Delete 92 | ```python 93 | user = User(name="Marie") 94 | user.save(wait_for=True) 95 | 96 | user.delete(wait_for=True) 97 | ``` 98 | 99 | ### Sessions 100 | Sessions are inspired by [SQL Alchemy](https://docs.sqlalchemy.org/en/14/orm/tutorial.html)'s sessions, and are used for simplifying bulk operations using the Elasticsearch client. From what I've seen, the ES client makes it pretty hard to use the bulk API, so they created bulk helpers (which in turn have incomplete/wrong docs). 101 | 102 | 103 | ```python 104 | john = User(name="John") 105 | sarah = User(name="Sarah") 106 | 107 | with Session() as session: 108 | session.save(john) 109 | session.save(sarah) 110 | session.commit() 111 | ``` 112 | 113 | With an ORM, bulk operations can be exposed neatly through a simple API. Pydastic also offers more informative errors on issues encountered during bulk operations. This is possible by suppressing the built-in elastic client errors and extracting more verbose ones instead. 114 | 115 | Example error: 116 | 117 | ```json 118 | pydastic.error.BulkError: [ 119 | { 120 | "update": { 121 | "_index": "user", 122 | "_type": "_doc", 123 | "_id": "test", 124 | "status": 404, 125 | "error": { 126 | "type": "document_missing_exception", 127 | "reason": "[_doc][test]: document missing", 128 | "index_uuid": "cKD0254aQRWF-E2TMxHa4Q", 129 | "shard": "0", 130 | "index": "user" 131 | } 132 | } 133 | }, 134 | { 135 | "update": { 136 | "_index": "user", 137 | "_type": "_doc", 138 | "_id": "test2", 139 | "status": 404, 140 | "error": { 141 | "type": "document_missing_exception", 142 | "reason": "[_doc][test2]: document missing", 143 | "index_uuid": "cKD0254aQRWF-E2TMxHa4Q", 144 | "shard": "0", 145 | "index": "user" 146 | } 147 | } 148 | } 149 | ] 150 | ``` 151 | 152 | The sessions API will also be available through a context manager before the v1.0 release. 153 | 154 | 155 | ### Dynamic Index Support 156 | Pydastic also supports dynamic index specification. The model Metaclass index definition is still mandatory, but if an index is specified when performing operations, that will be used instead. 157 | The model Metaclass index is technically a fallback, although most users will probably be using a single index per model. For some users, multiple indices per model are needed (for example in B2B businesses, one user index per client/company is needed to keep the data separated between clients). 158 | 159 | ```python 160 | user = User(name="Marie") 161 | user.save(index="my-user", wait_for=True) 162 | 163 | user.delete(index="my-user", wait_for=True) 164 | ``` 165 | 166 | ### Search API 167 | Still haven't got an idea on how to wrap the underlying API productively. Unless I create a DSL from scratch or use elasticsearch-dsl (which I don't like due to lacking documentation), I can't really provide any value on top of the client's built-in search API. Give this a minute of thought and shoot me your suggestions if you come up with anything! 168 | 169 | 170 | ### Notes on testing 171 | When writing tests with Pydastic (even applies when writing tests with the elasticsearch client), remember to use the `wait_for=True` argument when executing operations. If this is not used, then the test will continue executing even if Elasticsearch hasn't propagated the change to all nodes, giving you weird results. 172 | 173 | For example if you save a document, then try getting it directly after, you'll get a document not found error. This is solved by using the wait_for argument in Pydastic (equivalent to `refresh="wait_for"` in Elasticsearch) 174 | 175 | Here is [a reference](https://elasticsearch-py.readthedocs.io/en/v8.2.0/api.html#elasticsearch.Elasticsearch.index) to where this argument is listed in the docs. 176 | 177 | It's also supported in the bulk helpers even though its not mentioned in their docs, but you wouldn't figure that out unless you dug into their source and traced back several function calls where `*args` `**kwargs` are just being forwarded across calls.. :) 178 | 179 | ## Support Elasticsearch Versions 180 | 181 | Part of the build flow is running the tests using elasticsearch 7.12.0 DB as well as a 7.12.0 elasticsearch-python client. Another part is using 8.1.2 as well (DB as well as client, as part of a build matrix). This ensures support for multiple versions. 182 | 183 | ## 📈 Releases 184 | 185 | None yet. 186 | 187 | You can see the list of available releases on the [GitHub Releases](https://github.com/ramiawar/pydastic/releases) page. 188 | 189 | We follow [Semantic Versions](https://semver.org/) specification. 190 | 191 | We use [`Release Drafter`](https://github.com/marketplace/actions/release-drafter). As pull requests are merged, a draft release is kept up-to-date listing the changes, ready to publish when you’re ready. With the categories option, you can categorize pull requests in release notes using labels. 192 | 193 | 194 | ## 🛡 License 195 | 196 | [![License](https://img.shields.io/github/license/ramiawar/pydastic)](https://github.com/ramiawar/pydastic/blob/master/LICENSE) 197 | 198 | This project is licensed under the terms of the `MIT` license. See [LICENSE](https://github.com/ramiawar/pydastic/blob/master/LICENSE) for more details. 199 | 200 | ## 📃 Citation 201 | 202 | ```bibtex 203 | @misc{pydastic, 204 | author = {Rami Awar}, 205 | title = {Pydastic is an elasticsearch python ORM based on Pydantic.}, 206 | year = {2022}, 207 | publisher = {GitHub}, 208 | journal = {GitHub repository}, 209 | howpublished = {\url{https://github.com/ramiawar/pydastic}} 210 | } 211 | ``` 212 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security 2 | 3 | ## 🔐 Reporting Security Issues 4 | 5 | > Do not open issues that might have security implications! 6 | > It is critical that security related issues are reported privately so we have time to address them before they become public knowledge. 7 | 8 | Vulnerabilities can be reported by emailing core members: 9 | 10 | - pydastic [rami.awar.ra@gmail.com](mailto:rami.awar.ra@gmail.com) 11 | 12 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 13 | 14 | - Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 15 | - Full paths of source file(s) related to the manifestation of the issue 16 | - The location of the affected source code (tag/branch/commit or direct URL) 17 | - Any special configuration required to reproduce the issue 18 | - Environment (e.g. Linux / Windows / macOS) 19 | - Step-by-step instructions to reproduce the issue 20 | - Proof-of-concept or exploit code (if possible) 21 | - Impact of the issue, including how an attacker might exploit the issue 22 | 23 | This information will help us triage your report more quickly. 24 | 25 | ## Preferred Languages 26 | 27 | We prefer all communications to be in English. 28 | -------------------------------------------------------------------------------- /assets/images/coverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | coverage 17 | coverage 18 | 96% 19 | 96% 20 | 21 | 22 | -------------------------------------------------------------------------------- /assets/images/pydastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RamiAwar/pydastic/87c6ac3e7c1c8c54fa2cbee88b61cc8a9df033ad/assets/images/pydastic.png -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "astroid" 3 | version = "2.11.4" 4 | description = "An abstract syntax tree for Python with inference support." 5 | category = "dev" 6 | optional = false 7 | python-versions = ">=3.6.2" 8 | 9 | [package.dependencies] 10 | lazy-object-proxy = ">=1.4.0" 11 | typed-ast = {version = ">=1.4.0,<2.0", markers = "implementation_name == \"cpython\" and python_version < \"3.8\""} 12 | typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} 13 | wrapt = ">=1.11,<2" 14 | 15 | [[package]] 16 | name = "atomicwrites" 17 | version = "1.4.0" 18 | description = "Atomic file writes." 19 | category = "dev" 20 | optional = false 21 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 22 | 23 | [[package]] 24 | name = "attrs" 25 | version = "21.4.0" 26 | description = "Classes Without Boilerplate" 27 | category = "dev" 28 | optional = false 29 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 30 | 31 | [package.extras] 32 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] 33 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 34 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] 35 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] 36 | 37 | [[package]] 38 | name = "bandit" 39 | version = "1.7.4" 40 | description = "Security oriented static analyser for python code." 41 | category = "dev" 42 | optional = false 43 | python-versions = ">=3.7" 44 | 45 | [package.dependencies] 46 | colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""} 47 | GitPython = ">=1.0.1" 48 | PyYAML = ">=5.3.1" 49 | stevedore = ">=1.20.0" 50 | 51 | [package.extras] 52 | test = ["coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "toml", "beautifulsoup4 (>=4.8.0)", "pylint (==1.9.4)"] 53 | toml = ["toml"] 54 | yaml = ["pyyaml"] 55 | 56 | [[package]] 57 | name = "black" 58 | version = "22.3.0" 59 | description = "The uncompromising code formatter." 60 | category = "dev" 61 | optional = false 62 | python-versions = ">=3.6.2" 63 | 64 | [package.dependencies] 65 | click = ">=8.0.0" 66 | mypy-extensions = ">=0.4.3" 67 | pathspec = ">=0.9.0" 68 | platformdirs = ">=2" 69 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 70 | typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} 71 | typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} 72 | 73 | [package.extras] 74 | colorama = ["colorama (>=0.4.3)"] 75 | d = ["aiohttp (>=3.7.4)"] 76 | jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] 77 | uvloop = ["uvloop (>=0.15.2)"] 78 | 79 | [[package]] 80 | name = "certifi" 81 | version = "2021.10.8" 82 | description = "Python package for providing Mozilla's CA Bundle." 83 | category = "main" 84 | optional = false 85 | python-versions = "*" 86 | 87 | [[package]] 88 | name = "cfgv" 89 | version = "3.3.1" 90 | description = "Validate configuration and produce human readable error messages." 91 | category = "dev" 92 | optional = false 93 | python-versions = ">=3.6.1" 94 | 95 | [[package]] 96 | name = "charset-normalizer" 97 | version = "2.0.12" 98 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 99 | category = "dev" 100 | optional = false 101 | python-versions = ">=3.5.0" 102 | 103 | [package.extras] 104 | unicode_backport = ["unicodedata2"] 105 | 106 | [[package]] 107 | name = "click" 108 | version = "8.1.3" 109 | description = "Composable command line interface toolkit" 110 | category = "dev" 111 | optional = false 112 | python-versions = ">=3.7" 113 | 114 | [package.dependencies] 115 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 116 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 117 | 118 | [[package]] 119 | name = "colorama" 120 | version = "0.4.4" 121 | description = "Cross-platform colored terminal text." 122 | category = "dev" 123 | optional = false 124 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 125 | 126 | [[package]] 127 | name = "coverage" 128 | version = "6.3.2" 129 | description = "Code coverage measurement for Python" 130 | category = "dev" 131 | optional = false 132 | python-versions = ">=3.7" 133 | 134 | [package.dependencies] 135 | tomli = {version = "*", optional = true, markers = "extra == \"toml\""} 136 | 137 | [package.extras] 138 | toml = ["tomli"] 139 | 140 | [[package]] 141 | name = "coverage-badge" 142 | version = "1.1.0" 143 | description = "Generate coverage badges for Coverage.py." 144 | category = "dev" 145 | optional = false 146 | python-versions = "*" 147 | 148 | [package.dependencies] 149 | coverage = "*" 150 | 151 | [[package]] 152 | name = "darglint" 153 | version = "1.8.1" 154 | description = "A utility for ensuring Google-style docstrings stay up to date with the source code." 155 | category = "dev" 156 | optional = false 157 | python-versions = ">=3.6,<4.0" 158 | 159 | [[package]] 160 | name = "dill" 161 | version = "0.3.4" 162 | description = "serialize all of python" 163 | category = "dev" 164 | optional = false 165 | python-versions = ">=2.7, !=3.0.*" 166 | 167 | [package.extras] 168 | graph = ["objgraph (>=1.7.2)"] 169 | 170 | [[package]] 171 | name = "distlib" 172 | version = "0.3.4" 173 | description = "Distribution utilities" 174 | category = "dev" 175 | optional = false 176 | python-versions = "*" 177 | 178 | [[package]] 179 | name = "dparse" 180 | version = "0.5.1" 181 | description = "A parser for Python dependency files" 182 | category = "dev" 183 | optional = false 184 | python-versions = ">=3.5" 185 | 186 | [package.dependencies] 187 | packaging = "*" 188 | pyyaml = "*" 189 | toml = "*" 190 | 191 | [package.extras] 192 | pipenv = ["pipenv"] 193 | 194 | [[package]] 195 | name = "elastic-transport" 196 | version = "8.1.2" 197 | description = "Transport classes and utilities shared among Python Elastic client libraries" 198 | category = "main" 199 | optional = false 200 | python-versions = ">=3.6" 201 | 202 | [package.dependencies] 203 | certifi = "*" 204 | urllib3 = ">=1.26.2,<2" 205 | 206 | [package.extras] 207 | develop = ["pytest", "pytest-cov", "pytest-mock", "pytest-asyncio", "pytest-httpserver", "trustme", "mock", "requests", "aiohttp"] 208 | 209 | [[package]] 210 | name = "elasticsearch" 211 | version = "8.1.2" 212 | description = "Python client for Elasticsearch" 213 | category = "main" 214 | optional = false 215 | python-versions = ">=3.6, <4" 216 | 217 | [package.dependencies] 218 | elastic-transport = ">=8,<9" 219 | 220 | [package.extras] 221 | async = ["aiohttp (>=3,<4)"] 222 | requests = ["requests (>=2.4.0,<3.0.0)"] 223 | 224 | [[package]] 225 | name = "filelock" 226 | version = "3.6.0" 227 | description = "A platform independent file lock." 228 | category = "dev" 229 | optional = false 230 | python-versions = ">=3.7" 231 | 232 | [package.extras] 233 | docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] 234 | testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] 235 | 236 | [[package]] 237 | name = "gitdb" 238 | version = "4.0.9" 239 | description = "Git Object Database" 240 | category = "dev" 241 | optional = false 242 | python-versions = ">=3.6" 243 | 244 | [package.dependencies] 245 | smmap = ">=3.0.1,<6" 246 | 247 | [[package]] 248 | name = "gitpython" 249 | version = "3.1.27" 250 | description = "GitPython is a python library used to interact with Git repositories" 251 | category = "dev" 252 | optional = false 253 | python-versions = ">=3.7" 254 | 255 | [package.dependencies] 256 | gitdb = ">=4.0.1,<5" 257 | typing-extensions = {version = ">=3.7.4.3", markers = "python_version < \"3.8\""} 258 | 259 | [[package]] 260 | name = "identify" 261 | version = "2.5.0" 262 | description = "File identification library for Python" 263 | category = "dev" 264 | optional = false 265 | python-versions = ">=3.7" 266 | 267 | [package.extras] 268 | license = ["ukkonen"] 269 | 270 | [[package]] 271 | name = "idna" 272 | version = "3.3" 273 | description = "Internationalized Domain Names in Applications (IDNA)" 274 | category = "dev" 275 | optional = false 276 | python-versions = ">=3.5" 277 | 278 | [[package]] 279 | name = "importlib-metadata" 280 | version = "4.11.3" 281 | description = "Read metadata from Python packages" 282 | category = "main" 283 | optional = false 284 | python-versions = ">=3.7" 285 | 286 | [package.dependencies] 287 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 288 | zipp = ">=0.5" 289 | 290 | [package.extras] 291 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 292 | perf = ["ipython"] 293 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] 294 | 295 | [[package]] 296 | name = "iniconfig" 297 | version = "1.1.1" 298 | description = "iniconfig: brain-dead simple config-ini parsing" 299 | category = "dev" 300 | optional = false 301 | python-versions = "*" 302 | 303 | [[package]] 304 | name = "isort" 305 | version = "5.10.1" 306 | description = "A Python utility / library to sort Python imports." 307 | category = "dev" 308 | optional = false 309 | python-versions = ">=3.6.1,<4.0" 310 | 311 | [package.dependencies] 312 | colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"colors\""} 313 | 314 | [package.extras] 315 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 316 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 317 | colors = ["colorama (>=0.4.3,<0.5.0)"] 318 | plugins = ["setuptools"] 319 | 320 | [[package]] 321 | name = "lazy-object-proxy" 322 | version = "1.7.1" 323 | description = "A fast and thorough lazy object proxy." 324 | category = "dev" 325 | optional = false 326 | python-versions = ">=3.6" 327 | 328 | [[package]] 329 | name = "mccabe" 330 | version = "0.7.0" 331 | description = "McCabe checker, plugin for flake8" 332 | category = "dev" 333 | optional = false 334 | python-versions = ">=3.6" 335 | 336 | [[package]] 337 | name = "mypy" 338 | version = "0.942" 339 | description = "Optional static typing for Python" 340 | category = "dev" 341 | optional = false 342 | python-versions = ">=3.6" 343 | 344 | [package.dependencies] 345 | mypy-extensions = ">=0.4.3" 346 | tomli = ">=1.1.0" 347 | typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""} 348 | typing-extensions = ">=3.10" 349 | 350 | [package.extras] 351 | dmypy = ["psutil (>=4.0)"] 352 | python2 = ["typed-ast (>=1.4.0,<2)"] 353 | reports = ["lxml"] 354 | 355 | [[package]] 356 | name = "mypy-extensions" 357 | version = "0.4.3" 358 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 359 | category = "dev" 360 | optional = false 361 | python-versions = "*" 362 | 363 | [[package]] 364 | name = "nodeenv" 365 | version = "1.6.0" 366 | description = "Node.js virtual environment builder" 367 | category = "dev" 368 | optional = false 369 | python-versions = "*" 370 | 371 | [[package]] 372 | name = "packaging" 373 | version = "21.3" 374 | description = "Core utilities for Python packages" 375 | category = "dev" 376 | optional = false 377 | python-versions = ">=3.6" 378 | 379 | [package.dependencies] 380 | pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" 381 | 382 | [[package]] 383 | name = "pathspec" 384 | version = "0.9.0" 385 | description = "Utility library for gitignore style pattern matching of file paths." 386 | category = "dev" 387 | optional = false 388 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 389 | 390 | [[package]] 391 | name = "pbr" 392 | version = "5.9.0" 393 | description = "Python Build Reasonableness" 394 | category = "dev" 395 | optional = false 396 | python-versions = ">=2.6" 397 | 398 | [[package]] 399 | name = "platformdirs" 400 | version = "2.5.2" 401 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 402 | category = "dev" 403 | optional = false 404 | python-versions = ">=3.7" 405 | 406 | [package.extras] 407 | docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] 408 | test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] 409 | 410 | [[package]] 411 | name = "pluggy" 412 | version = "1.0.0" 413 | description = "plugin and hook calling mechanisms for python" 414 | category = "dev" 415 | optional = false 416 | python-versions = ">=3.6" 417 | 418 | [package.dependencies] 419 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 420 | 421 | [package.extras] 422 | dev = ["pre-commit", "tox"] 423 | testing = ["pytest", "pytest-benchmark"] 424 | 425 | [[package]] 426 | name = "pre-commit" 427 | version = "2.18.1" 428 | description = "A framework for managing and maintaining multi-language pre-commit hooks." 429 | category = "dev" 430 | optional = false 431 | python-versions = ">=3.7" 432 | 433 | [package.dependencies] 434 | cfgv = ">=2.0.0" 435 | identify = ">=1.0.0" 436 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 437 | nodeenv = ">=0.11.1" 438 | pyyaml = ">=5.1" 439 | toml = "*" 440 | virtualenv = ">=20.0.8" 441 | 442 | [[package]] 443 | name = "py" 444 | version = "1.11.0" 445 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 446 | category = "dev" 447 | optional = false 448 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 449 | 450 | [[package]] 451 | name = "pydantic" 452 | version = "1.9.0" 453 | description = "Data validation and settings management using python 3.6 type hinting" 454 | category = "main" 455 | optional = false 456 | python-versions = ">=3.6.1" 457 | 458 | [package.dependencies] 459 | typing-extensions = ">=3.7.4.3" 460 | 461 | [package.extras] 462 | dotenv = ["python-dotenv (>=0.10.4)"] 463 | email = ["email-validator (>=1.0.3)"] 464 | 465 | [[package]] 466 | name = "pydocstyle" 467 | version = "6.1.1" 468 | description = "Python docstring style checker" 469 | category = "dev" 470 | optional = false 471 | python-versions = ">=3.6" 472 | 473 | [package.dependencies] 474 | snowballstemmer = "*" 475 | 476 | [package.extras] 477 | toml = ["toml"] 478 | 479 | [[package]] 480 | name = "pylint" 481 | version = "2.13.7" 482 | description = "python code static checker" 483 | category = "dev" 484 | optional = false 485 | python-versions = ">=3.6.2" 486 | 487 | [package.dependencies] 488 | astroid = ">=2.11.3,<=2.12.0-dev0" 489 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 490 | dill = ">=0.2" 491 | isort = ">=4.2.5,<6" 492 | mccabe = ">=0.6,<0.8" 493 | platformdirs = ">=2.2.0" 494 | tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} 495 | typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} 496 | 497 | [package.extras] 498 | testutil = ["gitpython (>3)"] 499 | 500 | [[package]] 501 | name = "pyparsing" 502 | version = "3.0.8" 503 | description = "pyparsing module - Classes and methods to define and execute parsing grammars" 504 | category = "dev" 505 | optional = false 506 | python-versions = ">=3.6.8" 507 | 508 | [package.extras] 509 | diagrams = ["railroad-diagrams", "jinja2"] 510 | 511 | [[package]] 512 | name = "pytest" 513 | version = "7.1.1" 514 | description = "pytest: simple powerful testing with Python" 515 | category = "dev" 516 | optional = false 517 | python-versions = ">=3.7" 518 | 519 | [package.dependencies] 520 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 521 | attrs = ">=19.2.0" 522 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 523 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 524 | iniconfig = "*" 525 | packaging = "*" 526 | pluggy = ">=0.12,<2.0" 527 | py = ">=1.8.2" 528 | tomli = ">=1.0.0" 529 | 530 | [package.extras] 531 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] 532 | 533 | [[package]] 534 | name = "pytest-cov" 535 | version = "3.0.0" 536 | description = "Pytest plugin for measuring coverage." 537 | category = "dev" 538 | optional = false 539 | python-versions = ">=3.6" 540 | 541 | [package.dependencies] 542 | coverage = {version = ">=5.2.1", extras = ["toml"]} 543 | pytest = ">=4.6" 544 | 545 | [package.extras] 546 | testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] 547 | 548 | [[package]] 549 | name = "pytest-html" 550 | version = "3.1.1" 551 | description = "pytest plugin for generating HTML reports" 552 | category = "dev" 553 | optional = false 554 | python-versions = ">=3.6" 555 | 556 | [package.dependencies] 557 | pytest = ">=5.0,<6.0.0 || >6.0.0" 558 | pytest-metadata = "*" 559 | 560 | [[package]] 561 | name = "pytest-metadata" 562 | version = "2.0.0" 563 | description = "pytest plugin for test session metadata" 564 | category = "dev" 565 | optional = false 566 | python-versions = ">=3.7,<4.0" 567 | 568 | [package.dependencies] 569 | pytest = ">=7.1.1,<8.0.0" 570 | 571 | [[package]] 572 | name = "pyupgrade" 573 | version = "2.32.1" 574 | description = "A tool to automatically upgrade syntax for newer versions." 575 | category = "dev" 576 | optional = false 577 | python-versions = ">=3.7" 578 | 579 | [package.dependencies] 580 | tokenize-rt = ">=3.2.0" 581 | 582 | [[package]] 583 | name = "pyyaml" 584 | version = "6.0" 585 | description = "YAML parser and emitter for Python" 586 | category = "dev" 587 | optional = false 588 | python-versions = ">=3.6" 589 | 590 | [[package]] 591 | name = "requests" 592 | version = "2.27.1" 593 | description = "Python HTTP for Humans." 594 | category = "dev" 595 | optional = false 596 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 597 | 598 | [package.dependencies] 599 | certifi = ">=2017.4.17" 600 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 601 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 602 | urllib3 = ">=1.21.1,<1.27" 603 | 604 | [package.extras] 605 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 606 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 607 | 608 | [[package]] 609 | name = "safety" 610 | version = "1.10.3" 611 | description = "Checks installed dependencies for known vulnerabilities." 612 | category = "dev" 613 | optional = false 614 | python-versions = ">=3.5" 615 | 616 | [package.dependencies] 617 | Click = ">=6.0" 618 | dparse = ">=0.5.1" 619 | packaging = "*" 620 | requests = "*" 621 | 622 | [[package]] 623 | name = "six" 624 | version = "1.16.0" 625 | description = "Python 2 and 3 compatibility utilities" 626 | category = "dev" 627 | optional = false 628 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 629 | 630 | [[package]] 631 | name = "smmap" 632 | version = "5.0.0" 633 | description = "A pure Python implementation of a sliding window memory map manager" 634 | category = "dev" 635 | optional = false 636 | python-versions = ">=3.6" 637 | 638 | [[package]] 639 | name = "snowballstemmer" 640 | version = "2.2.0" 641 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 642 | category = "dev" 643 | optional = false 644 | python-versions = "*" 645 | 646 | [[package]] 647 | name = "stevedore" 648 | version = "3.5.0" 649 | description = "Manage dynamic plugins for Python applications" 650 | category = "dev" 651 | optional = false 652 | python-versions = ">=3.6" 653 | 654 | [package.dependencies] 655 | importlib-metadata = {version = ">=1.7.0", markers = "python_version < \"3.8\""} 656 | pbr = ">=2.0.0,<2.1.0 || >2.1.0" 657 | 658 | [[package]] 659 | name = "tokenize-rt" 660 | version = "4.2.1" 661 | description = "A wrapper around the stdlib `tokenize` which roundtrips." 662 | category = "dev" 663 | optional = false 664 | python-versions = ">=3.6.1" 665 | 666 | [[package]] 667 | name = "toml" 668 | version = "0.10.2" 669 | description = "Python Library for Tom's Obvious, Minimal Language" 670 | category = "dev" 671 | optional = false 672 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 673 | 674 | [[package]] 675 | name = "tomli" 676 | version = "2.0.1" 677 | description = "A lil' TOML parser" 678 | category = "dev" 679 | optional = false 680 | python-versions = ">=3.7" 681 | 682 | [[package]] 683 | name = "typed-ast" 684 | version = "1.5.3" 685 | description = "a fork of Python 2 and 3 ast modules with type comment support" 686 | category = "dev" 687 | optional = false 688 | python-versions = ">=3.6" 689 | 690 | [[package]] 691 | name = "typing-extensions" 692 | version = "4.2.0" 693 | description = "Backported and Experimental Type Hints for Python 3.7+" 694 | category = "main" 695 | optional = false 696 | python-versions = ">=3.7" 697 | 698 | [[package]] 699 | name = "urllib3" 700 | version = "1.26.9" 701 | description = "HTTP library with thread-safe connection pooling, file post, and more." 702 | category = "main" 703 | optional = false 704 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 705 | 706 | [package.extras] 707 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 708 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 709 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 710 | 711 | [[package]] 712 | name = "virtualenv" 713 | version = "20.14.1" 714 | description = "Virtual Python Environment builder" 715 | category = "dev" 716 | optional = false 717 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 718 | 719 | [package.dependencies] 720 | distlib = ">=0.3.1,<1" 721 | filelock = ">=3.2,<4" 722 | importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} 723 | platformdirs = ">=2,<3" 724 | six = ">=1.9.0,<2" 725 | 726 | [package.extras] 727 | docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] 728 | testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] 729 | 730 | [[package]] 731 | name = "wrapt" 732 | version = "1.14.1" 733 | description = "Module for decorators, wrappers and monkey patching." 734 | category = "dev" 735 | optional = false 736 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 737 | 738 | [[package]] 739 | name = "zipp" 740 | version = "3.8.0" 741 | description = "Backport of pathlib-compatible object wrapper for zip files" 742 | category = "main" 743 | optional = false 744 | python-versions = ">=3.7" 745 | 746 | [package.extras] 747 | docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] 748 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] 749 | 750 | [metadata] 751 | lock-version = "1.1" 752 | python-versions = "^3.7" 753 | content-hash = "b6abfa16cf652413475c2c1801f72a94190eaab7757d0ac43df2fe8ae13205e5" 754 | 755 | [metadata.files] 756 | astroid = [ 757 | {file = "astroid-2.11.4-py3-none-any.whl", hash = "sha256:da0632b7c046d8361dfe1b1abb2e085a38624961fabe2997565a9c06c1be9d9a"}, 758 | {file = "astroid-2.11.4.tar.gz", hash = "sha256:561dc6015eecce7e696ff7e3b40434bc56831afeff783f0ea853e19c4f635c06"}, 759 | ] 760 | atomicwrites = [ 761 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 762 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 763 | ] 764 | attrs = [ 765 | {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, 766 | {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, 767 | ] 768 | bandit = [ 769 | {file = "bandit-1.7.4-py3-none-any.whl", hash = "sha256:412d3f259dab4077d0e7f0c11f50f650cc7d10db905d98f6520a95a18049658a"}, 770 | {file = "bandit-1.7.4.tar.gz", hash = "sha256:2d63a8c573417bae338962d4b9b06fbc6080f74ecd955a092849e1e65c717bd2"}, 771 | ] 772 | black = [ 773 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, 774 | {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, 775 | {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, 776 | {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, 777 | {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, 778 | {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, 779 | {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, 780 | {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, 781 | {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, 782 | {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, 783 | {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, 784 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, 785 | {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, 786 | {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, 787 | {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, 788 | {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, 789 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, 790 | {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, 791 | {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, 792 | {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, 793 | {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, 794 | {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, 795 | {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, 796 | ] 797 | certifi = [ 798 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 799 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 800 | ] 801 | cfgv = [ 802 | {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, 803 | {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, 804 | ] 805 | charset-normalizer = [ 806 | {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, 807 | {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, 808 | ] 809 | click = [ 810 | {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, 811 | {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, 812 | ] 813 | colorama = [ 814 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 815 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 816 | ] 817 | coverage = [ 818 | {file = "coverage-6.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9b27d894748475fa858f9597c0ee1d4829f44683f3813633aaf94b19cb5453cf"}, 819 | {file = "coverage-6.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:37d1141ad6b2466a7b53a22e08fe76994c2d35a5b6b469590424a9953155afac"}, 820 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9987b0354b06d4df0f4d3e0ec1ae76d7ce7cbca9a2f98c25041eb79eec766f1"}, 821 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:26e2deacd414fc2f97dd9f7676ee3eaecd299ca751412d89f40bc01557a6b1b4"}, 822 | {file = "coverage-6.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd8bafa458b5c7d061540f1ee9f18025a68e2d8471b3e858a9dad47c8d41903"}, 823 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:46191097ebc381fbf89bdce207a6c107ac4ec0890d8d20f3360345ff5976155c"}, 824 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6f89d05e028d274ce4fa1a86887b071ae1755082ef94a6740238cd7a8178804f"}, 825 | {file = "coverage-6.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:58303469e9a272b4abdb9e302a780072c0633cdcc0165db7eec0f9e32f901e05"}, 826 | {file = "coverage-6.3.2-cp310-cp310-win32.whl", hash = "sha256:2fea046bfb455510e05be95e879f0e768d45c10c11509e20e06d8fcaa31d9e39"}, 827 | {file = "coverage-6.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:a2a8b8bcc399edb4347a5ca8b9b87e7524c0967b335fbb08a83c8421489ddee1"}, 828 | {file = "coverage-6.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f1555ea6d6da108e1999b2463ea1003fe03f29213e459145e70edbaf3e004aaa"}, 829 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5f4e1edcf57ce94e5475fe09e5afa3e3145081318e5fd1a43a6b4539a97e518"}, 830 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a15dc0a14008f1da3d1ebd44bdda3e357dbabdf5a0b5034d38fcde0b5c234b7"}, 831 | {file = "coverage-6.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21b7745788866028adeb1e0eca3bf1101109e2dc58456cb49d2d9b99a8c516e6"}, 832 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:8ce257cac556cb03be4a248d92ed36904a59a4a5ff55a994e92214cde15c5bad"}, 833 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b0be84e5a6209858a1d3e8d1806c46214e867ce1b0fd32e4ea03f4bd8b2e3359"}, 834 | {file = "coverage-6.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:acf53bc2cf7282ab9b8ba346746afe703474004d9e566ad164c91a7a59f188a4"}, 835 | {file = "coverage-6.3.2-cp37-cp37m-win32.whl", hash = "sha256:8bdde1177f2311ee552f47ae6e5aa7750c0e3291ca6b75f71f7ffe1f1dab3dca"}, 836 | {file = "coverage-6.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b31651d018b23ec463e95cf10070d0b2c548aa950a03d0b559eaa11c7e5a6fa3"}, 837 | {file = "coverage-6.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07e6db90cd9686c767dcc593dff16c8c09f9814f5e9c51034066cad3373b914d"}, 838 | {file = "coverage-6.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2c6dbb42f3ad25760010c45191e9757e7dce981cbfb90e42feef301d71540059"}, 839 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c76aeef1b95aff3905fb2ae2d96e319caca5b76fa41d3470b19d4e4a3a313512"}, 840 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cf5cfcb1521dc3255d845d9dca3ff204b3229401994ef8d1984b32746bb45ca"}, 841 | {file = "coverage-6.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8fbbdc8d55990eac1b0919ca69eb5a988a802b854488c34b8f37f3e2025fa90d"}, 842 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ec6bc7fe73a938933d4178c9b23c4e0568e43e220aef9472c4f6044bfc6dd0f0"}, 843 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9baff2a45ae1f17c8078452e9e5962e518eab705e50a0aa8083733ea7d45f3a6"}, 844 | {file = "coverage-6.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd9e830e9d8d89b20ab1e5af09b32d33e1a08ef4c4e14411e559556fd788e6b2"}, 845 | {file = "coverage-6.3.2-cp38-cp38-win32.whl", hash = "sha256:f7331dbf301b7289013175087636bbaf5b2405e57259dd2c42fdcc9fcc47325e"}, 846 | {file = "coverage-6.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:68353fe7cdf91f109fc7d474461b46e7f1f14e533e911a2a2cbb8b0fc8613cf1"}, 847 | {file = "coverage-6.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b78e5afb39941572209f71866aa0b206c12f0109835aa0d601e41552f9b3e620"}, 848 | {file = "coverage-6.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e21876082ed887baed0146fe222f861b5815455ada3b33b890f4105d806128d"}, 849 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34626a7eee2a3da12af0507780bb51eb52dca0e1751fd1471d0810539cefb536"}, 850 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ebf730d2381158ecf3dfd4453fbca0613e16eaa547b4170e2450c9707665ce7"}, 851 | {file = "coverage-6.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd6fe30bd519694b356cbfcaca9bd5c1737cddd20778c6a581ae20dc8c04def2"}, 852 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:96f8a1cb43ca1422f36492bebe63312d396491a9165ed3b9231e778d43a7fca4"}, 853 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:dd035edafefee4d573140a76fdc785dc38829fe5a455c4bb12bac8c20cfc3d69"}, 854 | {file = "coverage-6.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ca5aeb4344b30d0bec47481536b8ba1181d50dbe783b0e4ad03c95dc1296684"}, 855 | {file = "coverage-6.3.2-cp39-cp39-win32.whl", hash = "sha256:f5fa5803f47e095d7ad8443d28b01d48c0359484fec1b9d8606d0e3282084bc4"}, 856 | {file = "coverage-6.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:9548f10d8be799551eb3a9c74bbf2b4934ddb330e08a73320123c07f95cc2d92"}, 857 | {file = "coverage-6.3.2-pp36.pp37.pp38-none-any.whl", hash = "sha256:18d520c6860515a771708937d2f78f63cc47ab3b80cb78e86573b0a760161faf"}, 858 | {file = "coverage-6.3.2.tar.gz", hash = "sha256:03e2a7826086b91ef345ff18742ee9fc47a6839ccd517061ef8fa1976e652ce9"}, 859 | ] 860 | coverage-badge = [ 861 | {file = "coverage-badge-1.1.0.tar.gz", hash = "sha256:c824a106503e981c02821e7d32f008fb3984b2338aa8c3800ec9357e33345b78"}, 862 | {file = "coverage_badge-1.1.0-py2.py3-none-any.whl", hash = "sha256:e365d56e5202e923d1b237f82defd628a02d1d645a147f867ac85c58c81d7997"}, 863 | ] 864 | darglint = [ 865 | {file = "darglint-1.8.1-py3-none-any.whl", hash = "sha256:5ae11c259c17b0701618a20c3da343a3eb98b3bc4b5a83d31cdd94f5ebdced8d"}, 866 | {file = "darglint-1.8.1.tar.gz", hash = "sha256:080d5106df149b199822e7ee7deb9c012b49891538f14a11be681044f0bb20da"}, 867 | ] 868 | dill = [ 869 | {file = "dill-0.3.4-py2.py3-none-any.whl", hash = "sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f"}, 870 | {file = "dill-0.3.4.zip", hash = "sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675"}, 871 | ] 872 | distlib = [ 873 | {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, 874 | {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, 875 | ] 876 | dparse = [ 877 | {file = "dparse-0.5.1-py3-none-any.whl", hash = "sha256:e953a25e44ebb60a5c6efc2add4420c177f1d8404509da88da9729202f306994"}, 878 | {file = "dparse-0.5.1.tar.gz", hash = "sha256:a1b5f169102e1c894f9a7d5ccf6f9402a836a5d24be80a986c7ce9eaed78f367"}, 879 | ] 880 | elastic-transport = [ 881 | {file = "elastic-transport-8.1.2.tar.gz", hash = "sha256:869f7d668fb7738776639053fc87499caacbd1bdc7819f0de8025ac0e6cb29ce"}, 882 | {file = "elastic_transport-8.1.2-py3-none-any.whl", hash = "sha256:10914d0c5c268d9dcfee02cfbef861382d098309ba4eedab820062841bd214b3"}, 883 | ] 884 | elasticsearch = [ 885 | {file = "elasticsearch-8.1.2-py3-none-any.whl", hash = "sha256:2fd3c88bc5fef259ab936538287404b2c3c5956630944563722d5b16a4edc550"}, 886 | {file = "elasticsearch-8.1.2.tar.gz", hash = "sha256:bf031b44c483f6dc40d607186103d8a152ce4ade96e4ed4c656f2421f794e123"}, 887 | ] 888 | filelock = [ 889 | {file = "filelock-3.6.0-py3-none-any.whl", hash = "sha256:f8314284bfffbdcfa0ff3d7992b023d4c628ced6feb957351d4c48d059f56bc0"}, 890 | {file = "filelock-3.6.0.tar.gz", hash = "sha256:9cd540a9352e432c7246a48fe4e8712b10acb1df2ad1f30e8c070b82ae1fed85"}, 891 | ] 892 | gitdb = [ 893 | {file = "gitdb-4.0.9-py3-none-any.whl", hash = "sha256:8033ad4e853066ba6ca92050b9df2f89301b8fc8bf7e9324d412a63f8bf1a8fd"}, 894 | {file = "gitdb-4.0.9.tar.gz", hash = "sha256:bac2fd45c0a1c9cf619e63a90d62bdc63892ef92387424b855792a6cabe789aa"}, 895 | ] 896 | gitpython = [ 897 | {file = "GitPython-3.1.27-py3-none-any.whl", hash = "sha256:5b68b000463593e05ff2b261acff0ff0972df8ab1b70d3cdbd41b546c8b8fc3d"}, 898 | {file = "GitPython-3.1.27.tar.gz", hash = "sha256:1c885ce809e8ba2d88a29befeb385fcea06338d3640712b59ca623c220bb5704"}, 899 | ] 900 | identify = [ 901 | {file = "identify-2.5.0-py2.py3-none-any.whl", hash = "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f"}, 902 | {file = "identify-2.5.0.tar.gz", hash = "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2"}, 903 | ] 904 | idna = [ 905 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 906 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 907 | ] 908 | importlib-metadata = [ 909 | {file = "importlib_metadata-4.11.3-py3-none-any.whl", hash = "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6"}, 910 | {file = "importlib_metadata-4.11.3.tar.gz", hash = "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539"}, 911 | ] 912 | iniconfig = [ 913 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 914 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 915 | ] 916 | isort = [ 917 | {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, 918 | {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, 919 | ] 920 | lazy-object-proxy = [ 921 | {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, 922 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, 923 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, 924 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, 925 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, 926 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, 927 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, 928 | {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, 929 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, 930 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, 931 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, 932 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, 933 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, 934 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, 935 | {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, 936 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, 937 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, 938 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, 939 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, 940 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, 941 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, 942 | {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, 943 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, 944 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, 945 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, 946 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, 947 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, 948 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, 949 | {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, 950 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, 951 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, 952 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, 953 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, 954 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, 955 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, 956 | {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, 957 | {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, 958 | ] 959 | mccabe = [ 960 | {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, 961 | {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, 962 | ] 963 | mypy = [ 964 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5bf44840fb43ac4074636fd47ee476d73f0039f4f54e86d7265077dc199be24d"}, 965 | {file = "mypy-0.942-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dcd955f36e0180258a96f880348fbca54ce092b40fbb4b37372ae3b25a0b0a46"}, 966 | {file = "mypy-0.942-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6776e5fa22381cc761df53e7496a805801c1a751b27b99a9ff2f0ca848c7eca0"}, 967 | {file = "mypy-0.942-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:edf7237137a1a9330046dbb14796963d734dd740a98d5e144a3eb1d267f5f9ee"}, 968 | {file = "mypy-0.942-cp310-cp310-win_amd64.whl", hash = "sha256:64235137edc16bee6f095aba73be5334677d6f6bdb7fa03cfab90164fa294a17"}, 969 | {file = "mypy-0.942-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b840cfe89c4ab6386c40300689cd8645fc8d2d5f20101c7f8bd23d15fca14904"}, 970 | {file = "mypy-0.942-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b184db8c618c43c3a31b32ff00cd28195d39e9c24e7c3b401f3db7f6e5767f5"}, 971 | {file = "mypy-0.942-cp36-cp36m-win_amd64.whl", hash = "sha256:1a0459c333f00e6a11cbf6b468b870c2b99a906cb72d6eadf3d1d95d38c9352c"}, 972 | {file = "mypy-0.942-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4c3e497588afccfa4334a9986b56f703e75793133c4be3a02d06a3df16b67a58"}, 973 | {file = "mypy-0.942-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f6ad963172152e112b87cc7ec103ba0f2db2f1cd8997237827c052a3903eaa6"}, 974 | {file = "mypy-0.942-cp37-cp37m-win_amd64.whl", hash = "sha256:0e2dd88410937423fba18e57147dd07cd8381291b93d5b1984626f173a26543e"}, 975 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:246e1aa127d5b78488a4a0594bd95f6d6fb9d63cf08a66dafbff8595d8891f67"}, 976 | {file = "mypy-0.942-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8d3ba77e56b84cd47a8ee45b62c84b6d80d32383928fe2548c9a124ea0a725c"}, 977 | {file = "mypy-0.942-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2bc249409a7168d37c658e062e1ab5173300984a2dada2589638568ddc1db02b"}, 978 | {file = "mypy-0.942-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9521c1265ccaaa1791d2c13582f06facf815f426cd8b07c3a485f486a8ffc1f3"}, 979 | {file = "mypy-0.942-cp38-cp38-win_amd64.whl", hash = "sha256:e865fec858d75b78b4d63266c9aff770ecb6a39dfb6d6b56c47f7f8aba6baba8"}, 980 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:6ce34a118d1a898f47def970a2042b8af6bdcc01546454726c7dd2171aa6dfca"}, 981 | {file = "mypy-0.942-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:10daab80bc40f84e3f087d896cdb53dc811a9f04eae4b3f95779c26edee89d16"}, 982 | {file = "mypy-0.942-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3841b5433ff936bff2f4dc8d54cf2cdbfea5d8e88cedfac45c161368e5770ba6"}, 983 | {file = "mypy-0.942-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6f7106cbf9cc2f403693bf50ed7c9fa5bb3dfa9007b240db3c910929abe2a322"}, 984 | {file = "mypy-0.942-cp39-cp39-win_amd64.whl", hash = "sha256:7742d2c4e46bb5017b51c810283a6a389296cda03df805a4f7869a6f41246534"}, 985 | {file = "mypy-0.942-py3-none-any.whl", hash = "sha256:a1b383fe99678d7402754fe90448d4037f9512ce70c21f8aee3b8bf48ffc51db"}, 986 | {file = "mypy-0.942.tar.gz", hash = "sha256:17e44649fec92e9f82102b48a3bf7b4a5510ad0cd22fa21a104826b5db4903e2"}, 987 | ] 988 | mypy-extensions = [ 989 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 990 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 991 | ] 992 | nodeenv = [ 993 | {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, 994 | {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, 995 | ] 996 | packaging = [ 997 | {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, 998 | {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, 999 | ] 1000 | pathspec = [ 1001 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 1002 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 1003 | ] 1004 | pbr = [ 1005 | {file = "pbr-5.9.0-py2.py3-none-any.whl", hash = "sha256:e547125940bcc052856ded43be8e101f63828c2d94239ffbe2b327ba3d5ccf0a"}, 1006 | {file = "pbr-5.9.0.tar.gz", hash = "sha256:e8dca2f4b43560edef58813969f52a56cef023146cbb8931626db80e6c1c4308"}, 1007 | ] 1008 | platformdirs = [ 1009 | {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, 1010 | {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, 1011 | ] 1012 | pluggy = [ 1013 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 1014 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 1015 | ] 1016 | pre-commit = [ 1017 | {file = "pre_commit-2.18.1-py2.py3-none-any.whl", hash = "sha256:02226e69564ebca1a070bd1f046af866aa1c318dbc430027c50ab832ed2b73f2"}, 1018 | {file = "pre_commit-2.18.1.tar.gz", hash = "sha256:5d445ee1fa8738d506881c5d84f83c62bb5be6b2838e32207433647e8e5ebe10"}, 1019 | ] 1020 | py = [ 1021 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 1022 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 1023 | ] 1024 | pydantic = [ 1025 | {file = "pydantic-1.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cb23bcc093697cdea2708baae4f9ba0e972960a835af22560f6ae4e7e47d33f5"}, 1026 | {file = "pydantic-1.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1d5278bd9f0eee04a44c712982343103bba63507480bfd2fc2790fa70cd64cf4"}, 1027 | {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab624700dc145aa809e6f3ec93fb8e7d0f99d9023b713f6a953637429b437d37"}, 1028 | {file = "pydantic-1.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c8d7da6f1c1049eefb718d43d99ad73100c958a5367d30b9321b092771e96c25"}, 1029 | {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3c3b035103bd4e2e4a28da9da7ef2fa47b00ee4a9cf4f1a735214c1bcd05e0f6"}, 1030 | {file = "pydantic-1.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3011b975c973819883842c5ab925a4e4298dffccf7782c55ec3580ed17dc464c"}, 1031 | {file = "pydantic-1.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:086254884d10d3ba16da0588604ffdc5aab3f7f09557b998373e885c690dd398"}, 1032 | {file = "pydantic-1.9.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0fe476769acaa7fcddd17cadd172b156b53546ec3614a4d880e5d29ea5fbce65"}, 1033 | {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8e9dcf1ac499679aceedac7e7ca6d8641f0193c591a2d090282aaf8e9445a46"}, 1034 | {file = "pydantic-1.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1e4c28f30e767fd07f2ddc6f74f41f034d1dd6bc526cd59e63a82fe8bb9ef4c"}, 1035 | {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c86229333cabaaa8c51cf971496f10318c4734cf7b641f08af0a6fbf17ca3054"}, 1036 | {file = "pydantic-1.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:c0727bda6e38144d464daec31dff936a82917f431d9c39c39c60a26567eae3ed"}, 1037 | {file = "pydantic-1.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:dee5ef83a76ac31ab0c78c10bd7d5437bfdb6358c95b91f1ba7ff7b76f9996a1"}, 1038 | {file = "pydantic-1.9.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9c9bdb3af48e242838f9f6e6127de9be7063aad17b32215ccc36a09c5cf1070"}, 1039 | {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ee7e3209db1e468341ef41fe263eb655f67f5c5a76c924044314e139a1103a2"}, 1040 | {file = "pydantic-1.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b6037175234850ffd094ca77bf60fb54b08b5b22bc85865331dd3bda7a02fa1"}, 1041 | {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b2571db88c636d862b35090ccf92bf24004393f85c8870a37f42d9f23d13e032"}, 1042 | {file = "pydantic-1.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8b5ac0f1c83d31b324e57a273da59197c83d1bb18171e512908fe5dc7278a1d6"}, 1043 | {file = "pydantic-1.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:bbbc94d0c94dd80b3340fc4f04fd4d701f4b038ebad72c39693c794fd3bc2d9d"}, 1044 | {file = "pydantic-1.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e0896200b6a40197405af18828da49f067c2fa1f821491bc8f5bde241ef3f7d7"}, 1045 | {file = "pydantic-1.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bdfdadb5994b44bd5579cfa7c9b0e1b0e540c952d56f627eb227851cda9db77"}, 1046 | {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:574936363cd4b9eed8acdd6b80d0143162f2eb654d96cb3a8ee91d3e64bf4cf9"}, 1047 | {file = "pydantic-1.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c556695b699f648c58373b542534308922c46a1cda06ea47bc9ca45ef5b39ae6"}, 1048 | {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f947352c3434e8b937e3aa8f96f47bdfe6d92779e44bb3f41e4c213ba6a32145"}, 1049 | {file = "pydantic-1.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5e48ef4a8b8c066c4a31409d91d7ca372a774d0212da2787c0d32f8045b1e034"}, 1050 | {file = "pydantic-1.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:96f240bce182ca7fe045c76bcebfa0b0534a1bf402ed05914a6f1dadff91877f"}, 1051 | {file = "pydantic-1.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:815ddebb2792efd4bba5488bc8fde09c29e8ca3227d27cf1c6990fc830fd292b"}, 1052 | {file = "pydantic-1.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6c5b77947b9e85a54848343928b597b4f74fc364b70926b3c4441ff52620640c"}, 1053 | {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c68c3bc88dbda2a6805e9a142ce84782d3930f8fdd9655430d8576315ad97ce"}, 1054 | {file = "pydantic-1.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a79330f8571faf71bf93667d3ee054609816f10a259a109a0738dac983b23c3"}, 1055 | {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f5a64b64ddf4c99fe201ac2724daada8595ada0d102ab96d019c1555c2d6441d"}, 1056 | {file = "pydantic-1.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a733965f1a2b4090a5238d40d983dcd78f3ecea221c7af1497b845a9709c1721"}, 1057 | {file = "pydantic-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cc6a4cb8a118ffec2ca5fcb47afbacb4f16d0ab8b7350ddea5e8ef7bcc53a16"}, 1058 | {file = "pydantic-1.9.0-py3-none-any.whl", hash = "sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3"}, 1059 | {file = "pydantic-1.9.0.tar.gz", hash = "sha256:742645059757a56ecd886faf4ed2441b9c0cd406079c2b4bee51bcc3fbcd510a"}, 1060 | ] 1061 | pydocstyle = [ 1062 | {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, 1063 | {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, 1064 | ] 1065 | pylint = [ 1066 | {file = "pylint-2.13.7-py3-none-any.whl", hash = "sha256:13ddbbd8872c804574149e81197c28877eba75224ba6b76cd8652fc31df55c1c"}, 1067 | {file = "pylint-2.13.7.tar.gz", hash = "sha256:911d3a97c808f7554643bcc5416028cfdc42eae34ed129b150741888c688d5d5"}, 1068 | ] 1069 | pyparsing = [ 1070 | {file = "pyparsing-3.0.8-py3-none-any.whl", hash = "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06"}, 1071 | {file = "pyparsing-3.0.8.tar.gz", hash = "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954"}, 1072 | ] 1073 | pytest = [ 1074 | {file = "pytest-7.1.1-py3-none-any.whl", hash = "sha256:92f723789a8fdd7180b6b06483874feca4c48a5c76968e03bb3e7f806a1869ea"}, 1075 | {file = "pytest-7.1.1.tar.gz", hash = "sha256:841132caef6b1ad17a9afde46dc4f6cfa59a05f9555aae5151f73bdf2820ca63"}, 1076 | ] 1077 | pytest-cov = [ 1078 | {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, 1079 | {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, 1080 | ] 1081 | pytest-html = [ 1082 | {file = "pytest-html-3.1.1.tar.gz", hash = "sha256:3ee1cf319c913d19fe53aeb0bc400e7b0bc2dbeb477553733db1dad12eb75ee3"}, 1083 | {file = "pytest_html-3.1.1-py3-none-any.whl", hash = "sha256:b7f82f123936a3f4d2950bc993c2c1ca09ce262c9ae12f9ac763a2401380b455"}, 1084 | ] 1085 | pytest-metadata = [ 1086 | {file = "pytest-metadata-2.0.0.tar.gz", hash = "sha256:08dcc2779f4393309dd6d341ea1ddc15265239b6c4d51671737e784406ec07dc"}, 1087 | {file = "pytest_metadata-2.0.0-py3-none-any.whl", hash = "sha256:e25f1a77ed02baf1d83911604247a70d60d7dcb970aa12be38e1ed58d4d38e65"}, 1088 | ] 1089 | pyupgrade = [ 1090 | {file = "pyupgrade-2.32.1-py2.py3-none-any.whl", hash = "sha256:d874f34870abadd7536c89678f9811076d5df93c13620f90a125355a2d31fa91"}, 1091 | {file = "pyupgrade-2.32.1.tar.gz", hash = "sha256:11e2c3e4e2e53a61b2d8852ed154ea5683887b6ac42561622ca8d89c94fd951a"}, 1092 | ] 1093 | pyyaml = [ 1094 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 1095 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 1096 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 1097 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 1098 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, 1099 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 1100 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 1101 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 1102 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 1103 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 1104 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, 1105 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 1106 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 1107 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 1108 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 1109 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 1110 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, 1111 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 1112 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 1113 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 1114 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 1115 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 1116 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, 1117 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 1118 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 1119 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 1120 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 1121 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 1122 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 1123 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, 1124 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 1125 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 1126 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 1127 | ] 1128 | requests = [ 1129 | {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, 1130 | {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, 1131 | ] 1132 | safety = [ 1133 | {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"}, 1134 | {file = "safety-1.10.3.tar.gz", hash = "sha256:30e394d02a20ac49b7f65292d19d38fa927a8f9582cdfd3ad1adbbc66c641ad5"}, 1135 | ] 1136 | six = [ 1137 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1138 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1139 | ] 1140 | smmap = [ 1141 | {file = "smmap-5.0.0-py3-none-any.whl", hash = "sha256:2aba19d6a040e78d8b09de5c57e96207b09ed71d8e55ce0959eeee6c8e190d94"}, 1142 | {file = "smmap-5.0.0.tar.gz", hash = "sha256:c840e62059cd3be204b0c9c9f74be2c09d5648eddd4580d9314c3ecde0b30936"}, 1143 | ] 1144 | snowballstemmer = [ 1145 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 1146 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 1147 | ] 1148 | stevedore = [ 1149 | {file = "stevedore-3.5.0-py3-none-any.whl", hash = "sha256:a547de73308fd7e90075bb4d301405bebf705292fa90a90fc3bcf9133f58616c"}, 1150 | {file = "stevedore-3.5.0.tar.gz", hash = "sha256:f40253887d8712eaa2bb0ea3830374416736dc8ec0e22f5a65092c1174c44335"}, 1151 | ] 1152 | tokenize-rt = [ 1153 | {file = "tokenize_rt-4.2.1-py2.py3-none-any.whl", hash = "sha256:08a27fa032a81cf45e8858d0ac706004fcd523e8463415ddf1442be38e204ea8"}, 1154 | {file = "tokenize_rt-4.2.1.tar.gz", hash = "sha256:0d4f69026fed520f8a1e0103aa36c406ef4661417f20ca643f913e33531b3b94"}, 1155 | ] 1156 | toml = [ 1157 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 1158 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 1159 | ] 1160 | tomli = [ 1161 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 1162 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 1163 | ] 1164 | typed-ast = [ 1165 | {file = "typed_ast-1.5.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ad3b48cf2b487be140072fb86feff36801487d4abb7382bb1929aaac80638ea"}, 1166 | {file = "typed_ast-1.5.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:542cd732351ba8235f20faa0fc7398946fe1a57f2cdb289e5497e1e7f48cfedb"}, 1167 | {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc2c11ae59003d4a26dda637222d9ae924387f96acae9492df663843aefad55"}, 1168 | {file = "typed_ast-1.5.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd5df1313915dbd70eaaa88c19030b441742e8b05e6103c631c83b75e0435ccc"}, 1169 | {file = "typed_ast-1.5.3-cp310-cp310-win_amd64.whl", hash = "sha256:e34f9b9e61333ecb0f7d79c21c28aa5cd63bec15cb7e1310d7d3da6ce886bc9b"}, 1170 | {file = "typed_ast-1.5.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f818c5b81966d4728fec14caa338e30a70dfc3da577984d38f97816c4b3071ec"}, 1171 | {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3042bfc9ca118712c9809201f55355479cfcdc17449f9f8db5e744e9625c6805"}, 1172 | {file = "typed_ast-1.5.3-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4fff9fdcce59dc61ec1b317bdb319f8f4e6b69ebbe61193ae0a60c5f9333dc49"}, 1173 | {file = "typed_ast-1.5.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8e0b8528838ffd426fea8d18bde4c73bcb4167218998cc8b9ee0a0f2bfe678a6"}, 1174 | {file = "typed_ast-1.5.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ef1d96ad05a291f5c36895d86d1375c0ee70595b90f6bb5f5fdbee749b146db"}, 1175 | {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed44e81517364cb5ba367e4f68fca01fba42a7a4690d40c07886586ac267d9b9"}, 1176 | {file = "typed_ast-1.5.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f60d9de0d087454c91b3999a296d0c4558c1666771e3460621875021bf899af9"}, 1177 | {file = "typed_ast-1.5.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9e237e74fd321a55c90eee9bc5d44be976979ad38a29bbd734148295c1ce7617"}, 1178 | {file = "typed_ast-1.5.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ee852185964744987609b40aee1d2eb81502ae63ee8eef614558f96a56c1902d"}, 1179 | {file = "typed_ast-1.5.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27e46cdd01d6c3a0dd8f728b6a938a6751f7bd324817501c15fb056307f918c6"}, 1180 | {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d64dabc6336ddc10373922a146fa2256043b3b43e61f28961caec2a5207c56d5"}, 1181 | {file = "typed_ast-1.5.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8cdf91b0c466a6c43f36c1964772918a2c04cfa83df8001ff32a89e357f8eb06"}, 1182 | {file = "typed_ast-1.5.3-cp38-cp38-win_amd64.whl", hash = "sha256:9cc9e1457e1feb06b075c8ef8aeb046a28ec351b1958b42c7c31c989c841403a"}, 1183 | {file = "typed_ast-1.5.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e20d196815eeffb3d76b75223e8ffed124e65ee62097e4e73afb5fec6b993e7a"}, 1184 | {file = "typed_ast-1.5.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:37e5349d1d5de2f4763d534ccb26809d1c24b180a477659a12c4bde9dd677d74"}, 1185 | {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f1a27592fac87daa4e3f16538713d705599b0a27dfe25518b80b6b017f0a6d"}, 1186 | {file = "typed_ast-1.5.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8831479695eadc8b5ffed06fdfb3e424adc37962a75925668deeb503f446c0a3"}, 1187 | {file = "typed_ast-1.5.3-cp39-cp39-win_amd64.whl", hash = "sha256:20d5118e494478ef2d3a2702d964dae830aedd7b4d3b626d003eea526be18718"}, 1188 | {file = "typed_ast-1.5.3.tar.gz", hash = "sha256:27f25232e2dd0edfe1f019d6bfaaf11e86e657d9bdb7b0956db95f560cceb2b3"}, 1189 | ] 1190 | typing-extensions = [ 1191 | {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, 1192 | {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, 1193 | ] 1194 | urllib3 = [ 1195 | {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, 1196 | {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, 1197 | ] 1198 | virtualenv = [ 1199 | {file = "virtualenv-20.14.1-py2.py3-none-any.whl", hash = "sha256:e617f16e25b42eb4f6e74096b9c9e37713cf10bf30168fb4a739f3fa8f898a3a"}, 1200 | {file = "virtualenv-20.14.1.tar.gz", hash = "sha256:ef589a79795589aada0c1c5b319486797c03b67ac3984c48c669c0e4f50df3a5"}, 1201 | ] 1202 | wrapt = [ 1203 | {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, 1204 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, 1205 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, 1206 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, 1207 | {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, 1208 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, 1209 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, 1210 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, 1211 | {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, 1212 | {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, 1213 | {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, 1214 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, 1215 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, 1216 | {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, 1217 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, 1218 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, 1219 | {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, 1220 | {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, 1221 | {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, 1222 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, 1223 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, 1224 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, 1225 | {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, 1226 | {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, 1227 | {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, 1228 | {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, 1229 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, 1230 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, 1231 | {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, 1232 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, 1233 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, 1234 | {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, 1235 | {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, 1236 | {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, 1237 | {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, 1238 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, 1239 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, 1240 | {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, 1241 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, 1242 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, 1243 | {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, 1244 | {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, 1245 | {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, 1246 | {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, 1247 | {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, 1248 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, 1249 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, 1250 | {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, 1251 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, 1252 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, 1253 | {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, 1254 | {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, 1255 | {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, 1256 | {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, 1257 | {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, 1258 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, 1259 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, 1260 | {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, 1261 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, 1262 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, 1263 | {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, 1264 | {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, 1265 | {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, 1266 | {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, 1267 | ] 1268 | zipp = [ 1269 | {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, 1270 | {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, 1271 | ] 1272 | -------------------------------------------------------------------------------- /pydastic/__init__.py: -------------------------------------------------------------------------------- 1 | # type: ignore[attr-defined] 2 | """Pydastic is an elasticsearch python ORM based on Pydantic.""" 3 | 4 | import sys 5 | 6 | if sys.version_info >= (3, 8): 7 | from importlib import metadata as importlib_metadata 8 | else: 9 | import importlib_metadata 10 | 11 | 12 | def get_version() -> str: 13 | try: 14 | return importlib_metadata.version(__name__) 15 | except importlib_metadata.PackageNotFoundError: # pragma: no cover 16 | return "unknown" 17 | 18 | 19 | version: str = get_version() 20 | 21 | from pydastic.error import ( 22 | InvalidElasticsearchResponse, 23 | InvalidModelError, 24 | NotFoundError, 25 | ) 26 | from pydastic.model import ESModel 27 | from pydastic.pydastic import PydasticClient, connect 28 | from pydastic.session import Session 29 | 30 | __all__ = [ 31 | "ESModel", 32 | "Session", 33 | "NotFoundError", 34 | "InvalidModelError", 35 | "InvalidElasticsearchResponse", 36 | "PydasticClient", 37 | "connect", 38 | ] 39 | -------------------------------------------------------------------------------- /pydastic/error.py: -------------------------------------------------------------------------------- 1 | class NotFoundError(Exception): 2 | ... 3 | 4 | 5 | class IndexDoesNotFoundError(Exception): 6 | ... 7 | 8 | 9 | class InvalidElasticsearchResponse(Exception): 10 | ... 11 | 12 | 13 | class InvalidModelError(Exception): 14 | ... 15 | 16 | 17 | class BulkError(Exception): 18 | ... 19 | -------------------------------------------------------------------------------- /pydastic/model.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | from datetime import datetime 3 | from typing import Any, Callable, Dict, Optional, Tuple, Type, TypeVar, Union 4 | 5 | from elasticsearch import NotFoundError as ElasticNotFoundError 6 | from pydantic import BaseModel, Field 7 | from pydantic.fields import FieldInfo 8 | from pydantic.main import ModelMetaclass 9 | 10 | from pydastic.error import InvalidElasticsearchResponse, NotFoundError 11 | from pydastic.pydastic import _client 12 | 13 | _T = TypeVar("_T") 14 | 15 | 16 | def __dataclass_transform__( 17 | *, 18 | eq_default: bool = True, 19 | order_default: bool = False, 20 | kw_only_default: bool = False, 21 | field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), 22 | ) -> Callable[[_T], _T]: 23 | """Decorator to allow python language servers to autocomplete ESModel instances""" 24 | return lambda a: a 25 | 26 | 27 | @__dataclass_transform__(kw_only_default=True, field_descriptors=(Field, FieldInfo)) 28 | class ESModelMeta(ModelMetaclass): 29 | """Abstract ESModel Metaclass 30 | This Metaclass ensures that any concrete implementations of ESModel 31 | include all necessary definitions, ex. Meta internal class 32 | """ 33 | 34 | def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any], **kwds: Any): 35 | base_model = super().__new__(cls, name, bases, namespace, **kwds) 36 | meta = base_model.__dict__.get("Meta", False) 37 | if not meta: 38 | raise NotImplementedError("Internal 'Meta' not implemented") 39 | 40 | # Check existence of index name 41 | if not meta.__dict__.get("index", False): 42 | raise NotImplementedError("'index' property is missing from internal Meta class definition") 43 | 44 | return base_model 45 | 46 | 47 | M = TypeVar("M", bound="ESModel") 48 | 49 | 50 | class ESModel(BaseModel, metaclass=ESModelMeta): 51 | id: Optional[str] = Field(default=None) 52 | 53 | class Meta: 54 | @property 55 | def index(self) -> str: 56 | """Elasticsearch index name associated with this model class""" 57 | raise NotImplementedError 58 | 59 | class Config: 60 | allow_population_by_field_name = True 61 | extra = "allow" 62 | json_encoders = {datetime: lambda dt: dt.isoformat()} 63 | 64 | def to_es(self: Type[M], **kwargs) -> Dict: 65 | """Generates an dictionary equivalent to what elasticsearch returns in the '_source' property of a response. This excludes the id property. 66 | 67 | Args: 68 | **kwargs: Pydantic .dict() options 69 | 70 | Returns: 71 | Dict 72 | """ 73 | exclude_unset = kwargs.pop( 74 | "exclude_unset", 75 | False, # Set as false so that default values are also stored 76 | ) 77 | 78 | exclude: set = kwargs.pop("exclude", {"id"}) 79 | if "id" not in exclude: 80 | exclude.add("id") 81 | 82 | d = self.dict(exclude=exclude, exclude_unset=exclude_unset, **kwargs) 83 | 84 | # Encode datetime fields 85 | for k, v in d.items(): 86 | if isinstance(v, datetime): 87 | d[k] = v.isoformat() 88 | 89 | return d 90 | 91 | @classmethod 92 | def from_es(cls: Type[M], data: Dict[str, Any]) -> M: 93 | """Returns an ESModel from an elasticsearch document that has _id, _source 94 | 95 | Args: 96 | data (Dict[str, Any]): elasticsearch document that has _id, _source 97 | 98 | Raises: 99 | InvalidElasticsearchResponse: raised if an invalid elasticsearch document format is provided 100 | 101 | Returns: 102 | ESModel 103 | """ 104 | if not data: 105 | return None 106 | 107 | source = data.get("_source") 108 | id = data.get("_id") 109 | 110 | if not source or not id: 111 | raise InvalidElasticsearchResponse 112 | 113 | model = cls(**source) 114 | model.id = id 115 | 116 | return model 117 | 118 | def save(self: Type[M], index: Optional[str] = None, wait_for: Optional[bool] = False): 119 | """Indexes document into elasticsearch. 120 | If document already exists, existing document will be updated as per native elasticsearch index operation. 121 | If model instance includes an 'id' property, this will be used as the elasticsearch _id. 122 | If no 'id' is provided, then document will be indexed and elasticsearch will generate a suitable id that will be populated on the returned model. 123 | 124 | Args: 125 | index (str, optional): Index name 126 | wait_for (bool, optional): Waits for all shards to sync before returning response - useful when writing tests. Defaults to False. 127 | """ 128 | doc = self.dict(exclude={"id"}) 129 | 130 | # Allow waiting for shards - useful when testing 131 | refresh = "false" 132 | if wait_for: 133 | refresh = "wait_for" 134 | 135 | # Use user-provided index if provided (dynamic index support) 136 | if not index: 137 | index = self.Meta.index 138 | 139 | res = _client.client.index(index=index, body=doc, id=self.id, refresh=refresh) 140 | self.id = res.get("_id") 141 | 142 | @classmethod 143 | def get(cls: Type[M], id: str, extra_fields: Optional[bool] = False, index: Optional[str] = None) -> M: 144 | """Fetches document and returns ESModel instance populated with properties. 145 | 146 | Args: 147 | id (str): Document id 148 | extra_fields (bool, Optional): Include fields found in elasticsearch but not part of the model definition 149 | index (str, optional): Index name 150 | 151 | Returns: 152 | ESModel 153 | 154 | Raises: 155 | NotFoundError: Returned if document not found 156 | """ 157 | source_includes = None 158 | if not extra_fields: 159 | fields: dict = copy(vars(cls).get("__fields__")) 160 | fields.pop("id", None) 161 | source_includes = list(fields.keys()) 162 | 163 | # Use user-provided index if provided (dynamic index support) 164 | if not index: 165 | index = cls.Meta.index 166 | 167 | try: 168 | res = _client.client.get(index=index, id=id, _source_includes=source_includes) 169 | except ElasticNotFoundError: 170 | raise NotFoundError(f"document with id {id} not found") 171 | 172 | model = cls.from_es(res) 173 | model.id = id 174 | 175 | return model 176 | 177 | def delete(self: Type[M], index: Optional[str] = None, wait_for: Optional[bool] = False): 178 | """Deletes document from elasticsearch. 179 | 180 | Args: 181 | index (str, optional): Index name 182 | wait_for (bool, optional): Waits for all shards to sync before returning response - useful when writing tests. Defaults to False. 183 | 184 | Raises: 185 | NotFoundError: Returned if document not found 186 | ValueError: Returned when id attribute missing from instance 187 | """ 188 | if not self.id: 189 | raise ValueError("id missing from object") 190 | 191 | # Allow waiting for shards - useful when testing 192 | refresh = "false" 193 | if wait_for: 194 | refresh = "wait_for" 195 | 196 | # Use user-provided index if provided (dynamic index support) 197 | if not index: 198 | index = self.Meta.index 199 | 200 | try: 201 | _client.client.delete(index=index, id=self.id, refresh=refresh) 202 | except ElasticNotFoundError: 203 | raise NotFoundError(f"document with id {id} not found") 204 | -------------------------------------------------------------------------------- /pydastic/pydastic.py: -------------------------------------------------------------------------------- 1 | import typing as t 2 | 3 | from elasticsearch import Elasticsearch 4 | 5 | 6 | class PydasticClient: 7 | client: Elasticsearch = None 8 | 9 | def __getattribute__(self, __name: str) -> t.Any: 10 | if __name == "client" and object.__getattribute__(self, __name) is None: 11 | raise AttributeError("client not initialized - make sure to call Pydastic.connect()") 12 | return object.__getattribute__(self, __name) 13 | 14 | def _set_client(self, client: Elasticsearch): 15 | object.__setattr__(self, "client", client) 16 | return client 17 | 18 | 19 | _client = PydasticClient() 20 | 21 | F = t.TypeVar("F", bound=t.Callable[..., t.Any]) 22 | 23 | 24 | class copy_signature(t.Generic[F]): 25 | def __init__(self, target: F) -> F: 26 | self._target = target 27 | 28 | def __call__(self, wrapped: t.Callable[..., t.Any]) -> F: 29 | def wrapped(*args, **kwargs): 30 | return _client._set_client(self._target(*args, **kwargs)) 31 | 32 | return wrapped 33 | 34 | 35 | @copy_signature(Elasticsearch) 36 | def connect(*args, **kwargs): 37 | ... 38 | -------------------------------------------------------------------------------- /pydastic/session.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import List, Optional 3 | 4 | from elasticsearch.helpers import bulk 5 | 6 | from pydastic.error import BulkError, InvalidModelError 7 | from pydastic.model import ESModel 8 | from pydastic.pydastic import _client 9 | 10 | 11 | class Session: 12 | def __init__(self): 13 | # Initialize state 14 | self._operations = [] 15 | 16 | def __enter__(self): 17 | return self 18 | 19 | def __exit__(self, type, value, traceback): 20 | ... 21 | 22 | def save(self, model: ESModel, index: Optional[str] = None): 23 | """Save bulk operation 24 | 25 | Args: 26 | model (ESModel): Model to be indexed. If id is included, that id will be used in the indexing operation. 27 | index (Optional[str], optional): Dynamic index name to perform operation on. Defaults to index set in model Meta class. 28 | """ 29 | if not index: 30 | index = model.Meta.index 31 | 32 | doc = model.dict(exclude={"id"}) 33 | op = {"_index": index, "_op_type": "index", **doc} 34 | 35 | # Allow specifying id when indexing 36 | if model.id: 37 | op["_id"] = model.id 38 | 39 | self._operations.append(op) 40 | 41 | def update(self, model: ESModel, index: Optional[str] = None): 42 | """Update bulk operation 43 | 44 | Args: 45 | model (ESModel): Model to be updated. Must have an id set. 46 | index (Optional[str], optional): Dynamic index name to perform operation on. Defaults to index set in model Meta class. 47 | 48 | Raises: 49 | InvalidModelError: Raised when model id is missing 50 | """ 51 | if not index: 52 | index = model.Meta.index 53 | 54 | if not model.id: 55 | raise InvalidModelError("model id property is required for update operations") 56 | 57 | doc = model.dict(exclude={"id"}) 58 | op = {"_id": model.id, "_index": index, "_op_type": "update", "_source": {"doc": doc}} 59 | 60 | self._operations.append(op) 61 | 62 | def commit(self, wait_for: Optional[bool] = False, raise_on_error: Optional[bool] = True) -> Optional[List[dict]]: 63 | """Commits all saved operations to database. 64 | 65 | Args: 66 | wait_for (Optional[bool], optional): Waits for all shards to sync before returning response - useful when writing tests. Defaults to False. 67 | raise_on_error (Optional[bool], optional): If set to False, errors are returned as an array of dicts. Defaults to True. 68 | 69 | Returns: 70 | List[dict]: Optionally returns list of errors 71 | 72 | Raises: 73 | BulkError: If any errors are encountered during executing the bulk operations and raise_on_error is True, this error is thrown, forwarding the ES errors all at once in a meaningful way. 74 | """ 75 | refresh = "false" 76 | if wait_for: 77 | refresh = "wait_for" 78 | 79 | results = bulk(client=_client.client, actions=self._operations, refresh=refresh, raise_on_error=False) 80 | errors = results[1] 81 | 82 | if len(errors) != 0: 83 | if raise_on_error: 84 | # Pretty print error list 85 | raise BulkError(json.dumps(errors, indent=4)) 86 | else: 87 | return errors 88 | 89 | def delete(self, model: ESModel, index: Optional[str] = None): 90 | """Delete bulk operation 91 | 92 | Args: 93 | model (ESModel): Model to be deleted. Must have an id set. 94 | index (Optional[str], optional): Dynamic index name to perform operation on. Defaults to index set in model Meta. 95 | 96 | Raises: 97 | InvalidModelError: Raised when model id is missing 98 | """ 99 | if not index: 100 | index = model.Meta.index 101 | 102 | if not model.id: 103 | raise InvalidModelError("model id property is required for delete operations") 104 | 105 | op = {"_id": model.id, "_index": index, "_op_type": "delete"} 106 | self._operations.append(op) 107 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Poetry pyproject.toml: https://python-poetry.org/docs/pyproject/ 2 | [build-system] 3 | requires = ["poetry_core>=1.0.0"] 4 | build-backend = "poetry.core.masonry.api" 5 | 6 | [tool.poetry] 7 | name = "pydastic" 8 | version = "0.4.0" 9 | description = "Pydastic is an elasticsearch python ORM based on Pydantic." 10 | readme = "README.md" 11 | authors = ["pydastic "] 12 | license = "MIT" 13 | repository = "https://github.com/ramiawar/pydastic" 14 | homepage = "https://github.com/ramiawar/pydastic" 15 | 16 | # Keywords description https://python-poetry.org/docs/pyproject/#keywords 17 | keywords = [] #! Update me 18 | 19 | # Pypi classifiers: https://pypi.org/classifiers/ 20 | classifiers = [ #! Update me 21 | "Development Status :: 3 - Alpha", 22 | "Intended Audience :: Developers", 23 | "Operating System :: OS Independent", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | "License :: OSI Approved :: MIT License", 26 | "Programming Language :: Python :: 3", 27 | "Programming Language :: Python :: 3.7", 28 | "Programming Language :: Python :: 3.8", 29 | "Programming Language :: Python :: 3.9", 30 | ] 31 | 32 | 33 | 34 | [tool.poetry.dependencies] 35 | python = "^3.7" 36 | elasticsearch = ">=7.12.0, <=8.1.2" 37 | importlib_metadata = {version = "^4.5.0", python = "<3.8"} 38 | pydantic = "^1.9.0" 39 | 40 | [tool.poetry.dev-dependencies] 41 | bandit = "^1.7.1" 42 | black = {version = "^22.3.0", allow-prereleases = true} 43 | darglint = "^1.8.1" 44 | isort = {extras = ["colors"], version = "^5.10.1"} 45 | mypy = "^0.942" 46 | mypy-extensions = "^0.4.3" 47 | pre-commit = "^2.15.0" 48 | pydocstyle = "^6.1.1" 49 | pylint = "^2.11.1" 50 | pytest = "^7.1.1" 51 | pyupgrade = "^2.32.1" 52 | safety = "^1.10.3" 53 | coverage = "^6.1.2" 54 | coverage-badge = "^1.1.0" 55 | pytest-html = "^3.1.1" 56 | pytest-cov = "^3.0.0" 57 | 58 | [tool.black] 59 | # https://github.com/psf/black 60 | target-version = ["py37"] 61 | line-length = 200 62 | color = true 63 | 64 | exclude = ''' 65 | /( 66 | \.git 67 | | \.hg 68 | | \.mypy_cache 69 | | \.tox 70 | | \.venv 71 | | _build 72 | | buck-out 73 | | build 74 | | dist 75 | | env 76 | | venv 77 | )/ 78 | ''' 79 | 80 | [tool.isort] 81 | include_trailing_comma = true 82 | profile = "black" 83 | 84 | [tool.mypy] 85 | # https://mypy.readthedocs.io/en/latest/config_file.html#using-a-pyproject-toml-file 86 | python_version = 3.7 87 | pretty = true 88 | show_traceback = true 89 | color_output = true 90 | 91 | allow_redefinition = false 92 | check_untyped_defs = true 93 | disallow_any_generics = true 94 | disallow_incomplete_defs = true 95 | ignore_missing_imports = true 96 | implicit_reexport = false 97 | no_implicit_optional = true 98 | show_column_numbers = true 99 | show_error_codes = true 100 | show_error_context = true 101 | strict_equality = true 102 | strict_optional = true 103 | warn_no_return = true 104 | warn_redundant_casts = true 105 | warn_return_any = true 106 | warn_unreachable = true 107 | warn_unused_configs = true 108 | warn_unused_ignores = true 109 | 110 | 111 | [tool.pytest.ini_options] 112 | # https://docs.pytest.org/en/6.2.x/customize.html#pyproject-toml 113 | # Directories that are not visited by pytest collector: 114 | norecursedirs =["hooks", "*.egg", ".eggs", "dist", "build", "docs", ".tox", ".git", "__pycache__"] 115 | doctest_optionflags = ["NUMBER", "NORMALIZE_WHITESPACE", "IGNORE_EXCEPTION_DETAIL"] 116 | 117 | # Extra options: 118 | addopts = [ 119 | "--strict-markers", 120 | "--tb=short", 121 | "--doctest-modules", 122 | "--doctest-continue-on-failure", 123 | ] 124 | 125 | [tool.coverage.run] 126 | source = ["tests"] 127 | 128 | [coverage.paths] 129 | source = "pydastic" 130 | 131 | [coverage.run] 132 | branch = true 133 | 134 | [coverage.report] 135 | fail_under = 50 136 | show_missing = true 137 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2021.10.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version < "4" 2 | elasticsearch==7.17.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0" and python_version < "4") 3 | importlib-metadata==4.11.3; python_version < "3.8" 4 | pydantic==1.9.0; python_full_version >= "3.6.1" 5 | typing-extensions==4.1.1; python_version >= "3.7" and python_version < "3.8" and python_full_version >= "3.6.1" 6 | urllib3==1.26.9; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" 7 | zipp==3.8.0; python_version >= "3.7" and python_version < "3.8" 8 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [darglint] 2 | # https://github.com/terrencepreilly/darglint 3 | strictness = long 4 | docstring_style = google 5 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from elasticsearch import Elasticsearch 3 | from user import User 4 | 5 | from pydastic.pydastic import _client, connect 6 | 7 | 8 | @pytest.fixture() 9 | def es() -> Elasticsearch: 10 | connect(hosts="http://localhost:9200") 11 | _client.client.delete_by_query(index="_all", body={"query": {"match_all": {}}}, wait_for_completion=True, refresh=True) 12 | return _client.client 13 | 14 | 15 | @pytest.fixture() 16 | def user(es: Elasticsearch) -> User: 17 | user = User(name="John", phone="123456") 18 | user.save(wait_for=True) 19 | return user 20 | -------------------------------------------------------------------------------- /tests/test_model.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | from datetime import datetime 3 | from uuid import uuid4 4 | 5 | import pytest 6 | from elasticsearch import Elasticsearch 7 | from user import User 8 | 9 | from pydastic import ESModel, NotFoundError 10 | from pydastic.error import InvalidElasticsearchResponse 11 | 12 | 13 | def test_model_definition_yields_error_without_meta_class(): 14 | with pytest.raises(NotImplementedError): 15 | 16 | class User(ESModel): 17 | pass 18 | 19 | 20 | def test_model_definition_yields_error_without_index(): 21 | with pytest.raises(NotImplementedError): 22 | 23 | class User(ESModel): 24 | class Meta: 25 | pass 26 | 27 | 28 | def test_model_save_without_connection_raises_attribute_error(): 29 | with pytest.raises(AttributeError): 30 | user = User(name="Liam") 31 | user.save(wait_for=True) 32 | 33 | 34 | def test_model_save(es: Elasticsearch): 35 | user = User(name="John") 36 | user.save(wait_for=True) 37 | assert user.id != None 38 | 39 | res = es.get(index=user.Meta.index, id=user.id) 40 | assert res["found"] 41 | 42 | # Check that fields match exactly 43 | model = user.to_es() 44 | assert res["_source"] == model 45 | 46 | 47 | def test_model_save_with_index(es: Elasticsearch): 48 | preset_id = "sam@mail.com" 49 | user = User(id=preset_id, name="Sam") 50 | user.save(wait_for=True) 51 | 52 | res = es.get(index=user.Meta.index, id=preset_id) 53 | assert res["found"] 54 | 55 | model = user.to_es() 56 | assert res["_source"] == model 57 | 58 | 59 | def test_model_save_with_dynamic_index(es: Elasticsearch): 60 | preset_id = "abc@mail.com" 61 | user = User(id=preset_id, name="Sam") 62 | user.save(index="custom-user", wait_for=True) 63 | 64 | res = es.get(index="custom-user", id=preset_id) 65 | assert res["found"] 66 | 67 | model = user.to_es() 68 | assert res["_source"] == model 69 | 70 | 71 | def test_model_save_datetime_saved_as_isoformat(es: Elasticsearch): 72 | date = datetime.now() 73 | iso = date.isoformat() 74 | 75 | user = User(name="Brandon", last_login=date) 76 | user.save(wait_for=True) 77 | 78 | res = es.get(index=user.Meta.index, id=user.id) 79 | assert res["found"] 80 | assert res["_source"]["last_login"] == iso 81 | 82 | 83 | def test_model_save_to_update(es: Elasticsearch, user: User): 84 | # Update user details 85 | user_copy = deepcopy(user) 86 | 87 | dummy_name = "xxxxx" 88 | user.name = dummy_name 89 | 90 | user.save(wait_for=True) 91 | saved_user = User.get(id=user.id) 92 | 93 | assert saved_user.name == user.name 94 | 95 | # Change name back to compare with old object 96 | saved_user.name = user_copy.name 97 | assert saved_user == user_copy 98 | 99 | 100 | def test_model_save_additional_fields(es: Elasticsearch): 101 | extra_fields = {"name": "John", "location": "Seattle", "manager_ids": ["Pam", "Sam"]} 102 | res = es.index(index=User.Meta.index, body=extra_fields) 103 | 104 | user = User.get(res["_id"], extra_fields=True) 105 | 106 | # Confirm that user has these extra fields 107 | assert user.location == extra_fields["location"] 108 | assert user.manager_ids == extra_fields["manager_ids"] 109 | 110 | # Check that extra fields dict is exact subset 111 | user_dict = user.dict() 112 | assert dict(user_dict, **extra_fields) == user_dict 113 | 114 | 115 | def test_model_ignores_additional_fields(es: Elasticsearch): 116 | extra_fields = {"name": "John", "location": "Seattle", "manager_ids": ["Pam", "Sam"]} 117 | res = es.index(index=User.Meta.index, body=extra_fields) 118 | 119 | user = User.get(res["_id"]) 120 | with pytest.raises(AttributeError): 121 | user.location 122 | 123 | with pytest.raises(AttributeError): 124 | user.manager_ids 125 | 126 | 127 | def test_model_get_fields_unaffected(es: Elasticsearch, user: User): 128 | """Bug where fields get overwritten when model is fetched and ID is popped out""" 129 | User.get(id=user.id) 130 | assert "id" in User.__fields__ 131 | 132 | 133 | def test_model_from_es(es: Elasticsearch): 134 | user = User(name="Alex") 135 | user.save(wait_for=True) 136 | 137 | res = es.get(index=user.Meta.index, id=user.id) 138 | assert res["found"] 139 | 140 | user_from_es = User.from_es(res) 141 | assert user == user_from_es 142 | 143 | 144 | def test_model_from_es_empty_data(): 145 | user = User.from_es({}) 146 | assert user is None 147 | 148 | 149 | def test_model_from_es_invalid_format(): 150 | res = {"does not": "include _source", "or": "_id"} 151 | 152 | with pytest.raises(InvalidElasticsearchResponse): 153 | User.from_es(res) 154 | 155 | 156 | def test_model_to_es(es: Elasticsearch): 157 | user = User(name="Claude") 158 | user.save(wait_for=True) 159 | es_from_user = user.to_es() 160 | 161 | res = es.get(index=user.Meta.index, id=user.id) 162 | assert res["_source"] == es_from_user 163 | 164 | 165 | def test_model_to_es_with_exclude(es: Elasticsearch): 166 | user = User(name="Carla") 167 | user.save(wait_for=True) 168 | es_from_user = user.to_es(exclude={"last_login", "phone"}) 169 | 170 | # Check that id excluded and fields excluded 171 | assert es_from_user == {"name": "Carla"} 172 | 173 | 174 | def test_model_get(es: Elasticsearch): 175 | user = User(name="Jean", phone="128") 176 | user.save(wait_for=True) 177 | 178 | get = User.get(id=user.id) 179 | assert get == user 180 | 181 | 182 | def test_model_get_with_dynamic_index(es: Elasticsearch): 183 | user = User(name="Philip", phone="128") 184 | user.save(index="custom", wait_for=True) 185 | 186 | get = User.get(index="custom", id=user.id) 187 | assert get == user 188 | 189 | 190 | def test_model_get_nonexistent_raises_error(es: Elasticsearch): 191 | with pytest.raises(NotFoundError): 192 | User.get(id=uuid4()) 193 | 194 | 195 | def test_model_delete_raises_error(es: Elasticsearch): 196 | user = User(name="James") 197 | 198 | with pytest.raises(ValueError): 199 | user.delete(wait_for=True) 200 | 201 | with pytest.raises(NotFoundError): 202 | user.id = "123456" 203 | user.delete(wait_for=True) 204 | 205 | 206 | def test_model_delete(es: Elasticsearch): 207 | user = User(name="Marie") 208 | user.save(wait_for=True) 209 | user.delete(wait_for=True) 210 | 211 | with pytest.raises(NotFoundError): 212 | User.get(id=user.id) 213 | 214 | 215 | def test_model_delete_with_dynamic_index(es: Elasticsearch): 216 | user = User(name="Marie") 217 | user.save(index="abc", wait_for=True) 218 | user.delete(index="abc", wait_for=True) 219 | 220 | with pytest.raises(NotFoundError): 221 | User.get(id=user.id, index="abc") 222 | 223 | 224 | def test_internal_meta_class_changes_limited_to_instance(): 225 | # Cannot modify Meta index to have a dynamic index name 226 | user = User(name="a") 227 | user.Meta.index = "dev-user" 228 | 229 | assert User.Meta.index == "dev-user" 230 | assert user.Meta.index == "dev-user" 231 | -------------------------------------------------------------------------------- /tests/test_session.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from elasticsearch import Elasticsearch 3 | from user import User 4 | 5 | from pydastic import ESModel, Session 6 | from pydastic.error import BulkError, InvalidModelError, NotFoundError 7 | 8 | 9 | def test_session_save_without_id(es: Elasticsearch): 10 | user = User(name="John") 11 | 12 | with Session() as session: 13 | session.save(user) 14 | session.commit(wait_for=True) 15 | 16 | res = es.search(index=user.Meta.index, body={"query": {"match_all": {}}}) 17 | assert len(res["hits"]["hits"]) == 1 18 | 19 | model = user.to_es() 20 | assert res["hits"]["hits"][0]["_source"] == model 21 | 22 | 23 | def test_session_save_with_id(es: Elasticsearch): 24 | user = User(id="john@mail.com", name="John") 25 | 26 | with Session() as session: 27 | session.save(user) 28 | session.commit(wait_for=True) 29 | 30 | res = es.get(index=user.Meta.index, id=user.id) 31 | assert res["found"] 32 | 33 | model = user.to_es() 34 | assert res["_source"] == model 35 | 36 | 37 | def test_session_with_bulk_error(es: Elasticsearch): 38 | user = User(id="test", name="Jane") # Not saved 39 | user2 = User(id="test2", name="test") 40 | 41 | with Session() as session: 42 | session.update(user) 43 | session.update(user2) 44 | 45 | with pytest.raises(BulkError): 46 | session.commit() 47 | 48 | 49 | def test_session_with_bulk_error_without_raise_on_error(es: Elasticsearch): 50 | user = User(id="test", name="Jane") # Not saved 51 | user2 = User(id="test2", name="test") 52 | 53 | with Session() as session: 54 | session.update(user) 55 | session.update(user2) 56 | 57 | errors = session.commit(raise_on_error=False) 58 | 59 | assert len(errors) == 2 60 | 61 | 62 | def test_session_update(es: Elasticsearch): 63 | user = User(id="test", name="Tran") 64 | user.save() 65 | 66 | with Session() as session: 67 | user.name = "ABC" 68 | session.update(user) 69 | session.commit() 70 | 71 | res = es.get(index=user.Meta.index, id=user.id) 72 | assert res["found"] 73 | 74 | user_from_es = User.from_es(res) 75 | assert user == user_from_es 76 | 77 | 78 | def test_session_update_without_id_raises_error(es: Elasticsearch): 79 | user = User(name="Fran") 80 | with Session() as session: 81 | with pytest.raises(InvalidModelError): 82 | session.update(user) 83 | 84 | 85 | def test_session_delete(es: Elasticsearch): 86 | user = User(name="Span") 87 | user.save() 88 | 89 | with Session() as session: 90 | session.delete(user) 91 | session.commit() 92 | 93 | with pytest.raises(NotFoundError): 94 | user.get(id=user.id) 95 | 96 | 97 | def test_session_delete_without_id_raises_error(es: Elasticsearch): 98 | user = User(name="Plan") 99 | 100 | with Session() as session: 101 | with pytest.raises(InvalidModelError): 102 | session.delete(user) 103 | -------------------------------------------------------------------------------- /tests/user.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from pydantic import Field 5 | 6 | from pydastic import ESModel 7 | 8 | 9 | class User(ESModel): 10 | name: str 11 | phone: Optional[str] 12 | last_login: datetime = Field(default_factory=datetime.now) 13 | 14 | class Meta: 15 | index = "user" 16 | -------------------------------------------------------------------------------- /todo_checker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_file() { 4 | local file=$1 5 | local match_pattern=$2 6 | 7 | local file_changes_with_context=$(git diff -U999999999 -p --cached --color=always -- $file) 8 | 9 | # From the diff, get the green lines starting with '+' and including '$match_pattern' 10 | local matched_additions=$(echo "$file_changes_with_context" | grep -C4 $'^\e\\[32m\+.*'"$match_pattern") 11 | 12 | if [ -n "$matched_additions" ]; then 13 | echo -e "\n$file additions match '$match_pattern':\n" 14 | 15 | for matched_line in $matched_additions 16 | do 17 | echo "$matched_line" 18 | done 19 | 20 | echo "Not committing, because $file matches $match_pattern" 21 | exit 1 22 | fi 23 | } 24 | 25 | # Actual hook logic: 26 | 27 | MATCH='TODO' 28 | for file in `git diff --cached -p --name-status | cut -c3-`; do 29 | for match_pattern in $MATCH 30 | do 31 | check_file $file $match_pattern 32 | done 33 | done 34 | exit 35 | --------------------------------------------------------------------------------