pynamodb-async 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. pynamodb_async-0.1.0/.gitignore +4 -0
  2. pynamodb_async-0.1.0/LICENSE +21 -0
  3. pynamodb_async-0.1.0/PKG-INFO +87 -0
  4. pynamodb_async-0.1.0/README.md +66 -0
  5. pynamodb_async-0.1.0/RELEASE_NOTES.rst +21 -0
  6. pynamodb_async-0.1.0/pyproject.toml +48 -0
  7. pynamodb_async-0.1.0/scripts/check_dist.py +113 -0
  8. pynamodb_async-0.1.0/scripts/release_notes.py +66 -0
  9. pynamodb_async-0.1.0/scripts/upstream_watch.py +45 -0
  10. pynamodb_async-0.1.0/src/pynamodb_async/__init__.py +21 -0
  11. pynamodb_async-0.1.0/src/pynamodb_async/_compat.py +198 -0
  12. pynamodb_async-0.1.0/src/pynamodb_async/_version.py +24 -0
  13. pynamodb_async-0.1.0/src/pynamodb_async/connection/__init__.py +13 -0
  14. pynamodb_async-0.1.0/src/pynamodb_async/connection/_botocore_private.py +30 -0
  15. pynamodb_async-0.1.0/src/pynamodb_async/connection/base.py +1310 -0
  16. pynamodb_async-0.1.0/src/pynamodb_async/connection/table.py +337 -0
  17. pynamodb_async-0.1.0/src/pynamodb_async/indexes.py +245 -0
  18. pynamodb_async-0.1.0/src/pynamodb_async/models.py +1179 -0
  19. pynamodb_async-0.1.0/src/pynamodb_async/pagination.py +222 -0
  20. pynamodb_async-0.1.0/src/pynamodb_async/transactions.py +144 -0
  21. pynamodb_async-0.1.0/tests/__init__.py +1 -0
  22. pynamodb_async-0.1.0/tests/conftest.py +11 -0
  23. pynamodb_async-0.1.0/tests/data.py +1308 -0
  24. pynamodb_async-0.1.0/tests/deep_eq.py +142 -0
  25. pynamodb_async-0.1.0/tests/integration/__init__.py +1 -0
  26. pynamodb_async-0.1.0/tests/integration/base_integration_test.py +176 -0
  27. pynamodb_async-0.1.0/tests/integration/binary_update_test.py +49 -0
  28. pynamodb_async-0.1.0/tests/integration/conftest.py +29 -0
  29. pynamodb_async-0.1.0/tests/integration/model_integration_test.py +145 -0
  30. pynamodb_async-0.1.0/tests/integration/table_integration_test.py +163 -0
  31. pynamodb_async-0.1.0/tests/integration/test_discriminator_index.py +115 -0
  32. pynamodb_async-0.1.0/tests/integration/test_transaction_integration.py +435 -0
  33. pynamodb_async-0.1.0/tests/response.py +24 -0
  34. pynamodb_async-0.1.0/tests/test_base_connection.py +1713 -0
  35. pynamodb_async-0.1.0/tests/test_client_cache.py +207 -0
  36. pynamodb_async-0.1.0/tests/test_connection_lifecycle.py +70 -0
  37. pynamodb_async-0.1.0/tests/test_model.py +3470 -0
  38. pynamodb_async-0.1.0/tests/test_model_close.py +21 -0
  39. pynamodb_async-0.1.0/tests/test_pagination.py +80 -0
  40. pynamodb_async-0.1.0/tests/test_signals.py +99 -0
  41. pynamodb_async-0.1.0/tests/test_table_connection.py +554 -0
  42. pynamodb_async-0.1.0/tests/test_transaction.py +151 -0
@@ -0,0 +1,4 @@
1
+ src/pynamodb_async/_version.py
2
+ dist/
3
+ __pycache__/
4
+ uv.lock
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 Jharrod LaFon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.5
2
+ Name: pynamodb-async
3
+ Version: 0.1.0
4
+ Summary: Native asyncio API for PynamoDB, built on aiobotocore
5
+ Project-URL: Source, https://github.com/zeroae/pynamodb-async
6
+ Project-URL: Issues, https://github.com/zeroae/pynamodb-async/issues
7
+ Project-URL: PynamoDB, https://github.com/pynamodb/PynamoDB
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Framework :: AsyncIO
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Requires-Python: >=3.10
18
+ Requires-Dist: aiobotocore>=2.13.0
19
+ Requires-Dist: pynamodb<6.2,>=6.1
20
+ Description-Content-Type: text/markdown
21
+
22
+ # pynamodb-async
23
+
24
+ A native asyncio API for [PynamoDB](https://github.com/pynamodb/PynamoDB), built on
25
+ [aiobotocore](https://github.com/aio-libs/aiobotocore). It runs next to official PynamoDB:
26
+ you keep using PynamoDB's attributes, indexes and expressions, and get awaitable models,
27
+ queries, batches and transactions.
28
+
29
+ ```
30
+ pip install pynamodb-async
31
+ ```
32
+
33
+ Needs Python 3.10+. This installs official `pynamodb` (6.1.x) and `aiobotocore`.
34
+
35
+ ## Usage
36
+
37
+ Declare a model with `Model` from `pynamodb_async.models` and attributes from `pynamodb.attributes`:
38
+
39
+ ```python
40
+ import pynamodb_async
41
+ from pynamodb.attributes import UnicodeAttribute
42
+ from pynamodb_async.models import Model
43
+
44
+
45
+ class User(Model):
46
+ class Meta:
47
+ table_name = "users"
48
+ region = "us-east-1"
49
+
50
+ email = UnicodeAttribute(hash_key=True)
51
+ name = UnicodeAttribute(null=True)
52
+
53
+
54
+ async def main():
55
+ async with pynamodb_async.connections():
56
+ await User("ada@example.com", name="Ada").save()
57
+ user = await User.get("ada@example.com")
58
+ async for u in User.query("ada@example.com"):
59
+ print(u.name)
60
+ ```
61
+
62
+ ## Closing clients and event loops
63
+
64
+ Every async client holds an HTTP session. Close it when you are done with
65
+ `await User.close()`, or wrap your code in `async with pynamodb_async.connections():`, which
66
+ closes every connection opened inside the block. A client belongs to the event loop that
67
+ created it, so use each model and `Connection` from one event loop at a time, and prefer one
68
+ long-lived event loop for the whole program.
69
+
70
+ ## Relationship to PynamoDB
71
+
72
+ This package works alongside official PynamoDB. Its code is generated from a private source
73
+ repository, a fork that also offers the same async API as a drop-in replacement for `pynamodb`.
74
+ This add-on has its own async `Connection.get_client()`. What it does not have are the fork's
75
+ sync additions: `pynamodb.models.Model.close()`, `pynamodb.connections()` and the sync
76
+ `pynamodb.connection.Connection.get_client()`. It also lacks upstream fixes merged after the
77
+ supported PynamoDB release (for example [#1302](https://github.com/pynamodb/PynamoDB/pull/1302))
78
+ until upstream ships them.
79
+
80
+ ## Contributing
81
+
82
+ The code here is generated from a private source repository. Please open issues in this
83
+ repository; pull requests are ported over by hand.
84
+
85
+ ## License
86
+
87
+ MIT, derived from PynamoDB. See `LICENSE`.
@@ -0,0 +1,66 @@
1
+ # pynamodb-async
2
+
3
+ A native asyncio API for [PynamoDB](https://github.com/pynamodb/PynamoDB), built on
4
+ [aiobotocore](https://github.com/aio-libs/aiobotocore). It runs next to official PynamoDB:
5
+ you keep using PynamoDB's attributes, indexes and expressions, and get awaitable models,
6
+ queries, batches and transactions.
7
+
8
+ ```
9
+ pip install pynamodb-async
10
+ ```
11
+
12
+ Needs Python 3.10+. This installs official `pynamodb` (6.1.x) and `aiobotocore`.
13
+
14
+ ## Usage
15
+
16
+ Declare a model with `Model` from `pynamodb_async.models` and attributes from `pynamodb.attributes`:
17
+
18
+ ```python
19
+ import pynamodb_async
20
+ from pynamodb.attributes import UnicodeAttribute
21
+ from pynamodb_async.models import Model
22
+
23
+
24
+ class User(Model):
25
+ class Meta:
26
+ table_name = "users"
27
+ region = "us-east-1"
28
+
29
+ email = UnicodeAttribute(hash_key=True)
30
+ name = UnicodeAttribute(null=True)
31
+
32
+
33
+ async def main():
34
+ async with pynamodb_async.connections():
35
+ await User("ada@example.com", name="Ada").save()
36
+ user = await User.get("ada@example.com")
37
+ async for u in User.query("ada@example.com"):
38
+ print(u.name)
39
+ ```
40
+
41
+ ## Closing clients and event loops
42
+
43
+ Every async client holds an HTTP session. Close it when you are done with
44
+ `await User.close()`, or wrap your code in `async with pynamodb_async.connections():`, which
45
+ closes every connection opened inside the block. A client belongs to the event loop that
46
+ created it, so use each model and `Connection` from one event loop at a time, and prefer one
47
+ long-lived event loop for the whole program.
48
+
49
+ ## Relationship to PynamoDB
50
+
51
+ This package works alongside official PynamoDB. Its code is generated from a private source
52
+ repository, a fork that also offers the same async API as a drop-in replacement for `pynamodb`.
53
+ This add-on has its own async `Connection.get_client()`. What it does not have are the fork's
54
+ sync additions: `pynamodb.models.Model.close()`, `pynamodb.connections()` and the sync
55
+ `pynamodb.connection.Connection.get_client()`. It also lacks upstream fixes merged after the
56
+ supported PynamoDB release (for example [#1302](https://github.com/pynamodb/PynamoDB/pull/1302))
57
+ until upstream ships them.
58
+
59
+ ## Contributing
60
+
61
+ The code here is generated from a private source repository. Please open issues in this
62
+ repository; pull requests are ported over by hand.
63
+
64
+ ## License
65
+
66
+ MIT, derived from PynamoDB. See `LICENSE`.
@@ -0,0 +1,21 @@
1
+ Release Notes
2
+ =============
3
+
4
+ v0.1.0
5
+ ------
6
+
7
+ First release of ``pynamodb-async``: a native asyncio API for PynamoDB, built on aiobotocore.
8
+ Requires Python 3.10+, official ``pynamodb>=6.1,<6.2`` and ``aiobotocore>=2.13.0``.
9
+
10
+ * ``pynamodb_async.models.Model`` with awaitable ``get``, ``save``, ``update``, ``delete``,
11
+ ``refresh``, ``count`` and table operations; ``query``, ``scan`` and ``batch_get`` return
12
+ async iterators; ``batch_write()`` is an async context manager.
13
+ * Async transactions: ``pynamodb_async.transactions.TransactGet`` / ``TransactWrite``.
14
+ * Close clients with ``await Model.close()`` or ``async with pynamodb_async.connections():``.
15
+ Models with the same settings on the same event loop share one client.
16
+
17
+ Known limitations:
18
+
19
+ * Use each model and connection from one event loop at a time.
20
+ * No built-in concurrency yet (for example parallel scan); gather independent operations
21
+ yourself.
@@ -0,0 +1,48 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.24", "hatch-vcs>=0.5", "setuptools-scm>=10"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "pynamodb-async"
7
+ dynamic = ["version"]
8
+ description = "Native asyncio API for PynamoDB, built on aiobotocore"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.10"
12
+ dependencies = ["pynamodb>=6.1,<6.2", "aiobotocore>=2.13.0"]
13
+ classifiers = [
14
+ "Development Status :: 4 - Beta",
15
+ "Framework :: AsyncIO",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+
23
+ [project.urls]
24
+ Source = "https://github.com/zeroae/pynamodb-async"
25
+ Issues = "https://github.com/zeroae/pynamodb-async/issues"
26
+ PynamoDB = "https://github.com/pynamodb/PynamoDB"
27
+
28
+ [dependency-groups]
29
+ test = ["pytest>=7", "pytest-asyncio>=0.23", "pytest-env", "pytest-mock", "freezegun", "blinker>=1.3,<2.0", "packaging"]
30
+
31
+ [tool.hatch.version]
32
+ source = "vcs"
33
+ tag-pattern = '^pynamodb-async-v(?P<version>\d+(?:\.\d+)*(?:(?:a|b|rc|\.post|\.dev)\d+)?)$'
34
+ raw-options = { search_parent_directories = true, fallback_version = "0.1.0.dev0", scm = { git = { describe_command = "git describe --dirty --tags --long --match 'pynamodb-async-v*'" } } }
35
+
36
+ [tool.hatch.build.hooks.vcs]
37
+ version-file = "src/pynamodb_async/_version.py"
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/pynamodb_async"]
41
+
42
+ [tool.hatch.build.targets.sdist]
43
+ include = ["src", "tests", "scripts", "README.md", "RELEASE_NOTES.rst", "LICENSE", "pyproject.toml"]
44
+
45
+ [tool.pytest.ini_options]
46
+ asyncio_mode = "auto"
47
+ markers = ["ddblocal: requires a DynamoDB Local server on localhost:8000"]
48
+ env = ["AWS_ACCESS_KEY_ID=1", "AWS_SECRET_ACCESS_KEY=2", "AWS_DEFAULT_REGION=us-east-1"]
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ # AUTO-GENERATED from scripts/check_dist.py by scripts/make_addon.py - DO NOT EDIT
3
+ """Check built distributions before they are released.
4
+
5
+ python scripts/check_dist.py DIST_DIR --name zae-pynamodb --package pynamodb \
6
+ --requires-python '>=3.7' [--min-version 6.2.0.dev0] [--expect-version 6.2.0] \
7
+ [--sdist-allow 'src/*' ...]
8
+
9
+ Checks every wheel and sdist in DIST_DIR: project name, Requires-Python, version (at least
10
+ --min-version; exactly --expect-version, with no local part), wheel files limited to the
11
+ package and its .dist-info, and - when --sdist-allow is given - sdist files limited to those
12
+ globs. Prints one "problem: ..." line per issue and exits 1 if there are any.
13
+ """
14
+ import argparse
15
+ import fnmatch
16
+ import sys
17
+ import tarfile
18
+ import zipfile
19
+ from email.parser import Parser
20
+ from pathlib import Path
21
+ from typing import List, NamedTuple, Optional, Sequence
22
+
23
+ from packaging.version import Version
24
+
25
+
26
+ class Options(NamedTuple):
27
+ name: str
28
+ package: str
29
+ requires_python: str
30
+ min_version: Optional[str]
31
+ expect_version: Optional[str]
32
+ sdist_allow: List[str]
33
+
34
+
35
+ def _check_metadata(label: str, text: str, opts: Options) -> List[str]:
36
+ meta = Parser().parsestr(text)
37
+ problems = []
38
+ if meta.get("Name") != opts.name:
39
+ problems.append("{}: Name is {!r}, expected {!r}".format(label, meta.get("Name"), opts.name))
40
+ if meta.get("Requires-Python") != opts.requires_python:
41
+ problems.append("{}: Requires-Python is {!r}, expected {!r}".format(
42
+ label, meta.get("Requires-Python"), opts.requires_python))
43
+ version = Version(meta.get("Version", "0"))
44
+ if opts.min_version and version < Version(opts.min_version):
45
+ problems.append("{}: version {} is below {}".format(label, version, opts.min_version))
46
+ if opts.expect_version and (version != Version(opts.expect_version) or version.local):
47
+ problems.append("{}: version {} is not exactly {}".format(label, version, opts.expect_version))
48
+ return problems
49
+
50
+
51
+ def check_wheel(path: Path, opts: Options) -> List[str]:
52
+ with zipfile.ZipFile(str(path)) as zf:
53
+ names = zf.namelist()
54
+ meta_names = [n for n in names if n.endswith(".dist-info/METADATA")]
55
+ if not meta_names:
56
+ return ["{}: no .dist-info/METADATA".format(path.name)]
57
+ problems = _check_metadata(path.name, zf.read(meta_names[0]).decode("utf-8"), opts)
58
+ for name in names:
59
+ top = name.split("/", 1)[0]
60
+ if top != opts.package and not top.endswith(".dist-info"):
61
+ problems.append("{}: unexpected file {}".format(path.name, name))
62
+ return problems
63
+
64
+
65
+ def check_sdist(path: Path, opts: Options) -> List[str]:
66
+ with tarfile.open(str(path), "r:gz") as tf:
67
+ members = [m for m in tf.getmembers() if m.isfile()]
68
+ rel = {m.name: m.name.split("/", 1)[1] if "/" in m.name else m.name for m in members}
69
+ pkg_info = [m for m in members if rel[m.name] == "PKG-INFO"]
70
+ if not pkg_info:
71
+ return ["{}: no PKG-INFO".format(path.name)]
72
+ handle = tf.extractfile(pkg_info[0])
73
+ problems = _check_metadata(path.name, handle.read().decode("utf-8") if handle else "", opts)
74
+ if opts.sdist_allow:
75
+ for full, name in sorted(rel.items()):
76
+ if not any(fnmatch.fnmatch(name, pattern) for pattern in opts.sdist_allow):
77
+ problems.append("{}: file not allowed in sdist: {}".format(path.name, name))
78
+ return problems
79
+
80
+
81
+ def main(argv: Sequence[str]) -> int:
82
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
83
+ parser.add_argument("dist_dir")
84
+ parser.add_argument("--name", required=True)
85
+ parser.add_argument("--package", required=True)
86
+ parser.add_argument("--requires-python", required=True)
87
+ parser.add_argument("--min-version")
88
+ parser.add_argument("--expect-version")
89
+ parser.add_argument("--sdist-allow", action="append", default=[])
90
+ args = parser.parse_args(argv)
91
+ opts = Options(args.name, args.package, args.requires_python,
92
+ args.min_version, args.expect_version, args.sdist_allow)
93
+ dist = Path(args.dist_dir)
94
+ wheels = sorted(dist.glob("*.whl"))
95
+ sdists = sorted(dist.glob("*.tar.gz"))
96
+ problems = []
97
+ if not wheels:
98
+ problems.append("no wheel in {}".format(dist))
99
+ if not sdists:
100
+ problems.append("no sdist in {}".format(dist))
101
+ for w in wheels:
102
+ problems += check_wheel(w, opts)
103
+ for s in sdists:
104
+ problems += check_sdist(s, opts)
105
+ for p in problems:
106
+ print("problem: " + p)
107
+ if not problems:
108
+ print("ok: {} wheel(s), {} sdist(s)".format(len(wheels), len(sdists)))
109
+ return 1 if problems else 0
110
+
111
+
112
+ if __name__ == "__main__":
113
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env python3
2
+ # AUTO-GENERATED from scripts/release_notes.py by scripts/make_addon.py - DO NOT EDIT
3
+ """Print the release-notes section for a version from an .rst file.
4
+
5
+ python scripts/release_notes.py docs/release_notes.rst 6.2.0 [--output notes.md]
6
+
7
+ A section starts at a title line ``v<version>`` underlined with a single repeated character
8
+ (``-``, ``=``, ``~`` or ``^``) and runs until the next underlined title. Exits 1 if the
9
+ section is missing or empty, so a release cannot go out without notes.
10
+ """
11
+ import argparse
12
+ import sys
13
+ from pathlib import Path
14
+ from typing import List, Optional, Sequence
15
+
16
+ _UNDERLINE_CHARS = "-=~^"
17
+
18
+
19
+ def _is_underline(line: str) -> bool:
20
+ s = line.strip()
21
+ return len(s) >= 3 and len(set(s)) == 1 and s[0] in _UNDERLINE_CHARS
22
+
23
+
24
+ def _is_title(lines: List[str], i: int) -> bool:
25
+ return (i + 1 < len(lines) and bool(lines[i].strip())
26
+ and not _is_underline(lines[i]) and _is_underline(lines[i + 1]))
27
+
28
+
29
+ def extract(text: str, version: str) -> Optional[str]:
30
+ lines = text.splitlines()
31
+ title = "v" + version
32
+ start = None
33
+ for i in range(len(lines)):
34
+ if lines[i].strip() == title and _is_title(lines, i):
35
+ start = i + 2
36
+ break
37
+ if start is None:
38
+ return None
39
+ end = len(lines)
40
+ for j in range(start, len(lines)):
41
+ if _is_title(lines, j):
42
+ end = j
43
+ break
44
+ body = "\n".join(lines[start:end]).strip()
45
+ return body or None
46
+
47
+
48
+ def main(argv: Sequence[str]) -> int:
49
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
50
+ parser.add_argument("rst_file")
51
+ parser.add_argument("version")
52
+ parser.add_argument("--output")
53
+ args = parser.parse_args(argv)
54
+ body = extract(Path(args.rst_file).read_text(encoding="utf-8"), args.version)
55
+ if body is None:
56
+ print("no non-empty 'v{}' section in {}".format(args.version, args.rst_file), file=sys.stderr)
57
+ return 1
58
+ if args.output:
59
+ Path(args.output).write_text(body + "\n", encoding="utf-8")
60
+ else:
61
+ print(body)
62
+ return 0
63
+
64
+
65
+ if __name__ == "__main__":
66
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env python3
2
+ # AUTO-GENERATED from scripts/upstream_watch.py by scripts/make_addon.py - DO NOT EDIT
3
+ """Check whether the latest official pynamodb release is inside the add-on's supported range.
4
+
5
+ python scripts/upstream_watch.py --spec '>=6.1,<6.2'
6
+
7
+ Prints the latest version. Exit 0 when it is supported, 2 when it is outside the range (the
8
+ weekly workflow then opens an issue to port the change and widen the range), 1 on errors.
9
+ """
10
+ import argparse
11
+ import json
12
+ import sys
13
+ from typing import Callable, Sequence
14
+ from urllib.request import urlopen
15
+
16
+ from packaging.specifiers import SpecifierSet
17
+ from packaging.version import Version
18
+
19
+ PYPI_URL = "https://pypi.org/pypi/pynamodb/json"
20
+
21
+
22
+ def is_supported(version: str, spec: str) -> bool:
23
+ return Version(version) in SpecifierSet(spec)
24
+
25
+
26
+ def latest_release() -> str:
27
+ with urlopen(PYPI_URL, timeout=30) as response:
28
+ return json.load(response)["info"]["version"]
29
+
30
+
31
+ def main(argv: Sequence[str], latest: Callable[[], str] = latest_release) -> int:
32
+ parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
33
+ parser.add_argument("--spec", required=True)
34
+ args = parser.parse_args(argv)
35
+ try:
36
+ version = latest()
37
+ except Exception as e:
38
+ print("error: could not read the latest pynamodb release: {}".format(e), file=sys.stderr)
39
+ return 1
40
+ print(version)
41
+ return 0 if is_supported(version, args.spec) else 2
42
+
43
+
44
+ if __name__ == "__main__":
45
+ sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,21 @@
1
+ # AUTO-GENERATED from pynamodb/asyncio/__init__.py by scripts/make_addon.py - DO NOT EDIT
2
+ """
3
+ Async API for PynamoDB, built on aiobotocore.
4
+
5
+ Install with ``pip install pynamodb-async`` (Python 3.10+). Usage mirrors the
6
+ sync API: ``from pynamodb_async.models import Model``.
7
+ """
8
+ import sys
9
+
10
+ if sys.version_info < (3, 10): # pragma: no cover
11
+ raise ImportError("pynamodb_async requires Python 3.10+; install pynamodb-async")
12
+ try:
13
+ import aiobotocore # noqa: F401
14
+ except ImportError as e:
15
+ raise ImportError(
16
+ "pynamodb_async requires aiobotocore; install pynamodb-async (Python 3.10+)"
17
+ ) from e
18
+
19
+ from pynamodb_async.connection.base import connections # noqa: E402
20
+
21
+ __all__ = ['connections']