├── .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 |
8 |
9 |
10 | [](https://github.com/RamiAwar/pydastic/actions/workflows/build.yml)
11 | [](https://pypi.org/project/pydastic/)
12 | [](https://github.com/ramiawar/pydastic/pulls?utf8=%E2%9C%93&q=is%3Apr%20author%3Aapp%2Fdependabot)
13 | [](https://github.com/psf/black)
14 |
15 | [](https://github.com/PyCQA/bandit)
16 | [](https://github.com/ramiawar/pydastic/blob/master/.pre-commit-config.yaml)
17 | [](https://github.com/ramiawar/pydastic/releases)
18 | [](https://github.com/ramiawar/pydastic/blob/master/LICENSE)
19 | 
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 | [](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 |
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 |
--------------------------------------------------------------------------------