Skip to content

Commit 8d794e1

Browse files
authored
drop support for python 3.6 (#1009)
1 parent 23eec8a commit 8d794e1

18 files changed

+78
-85
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
runs-on: ubuntu-latest
1212
strategy:
1313
matrix:
14-
python-version: ['3.6', '3.7', '3.8', 'pypy-3.6']
14+
python-version: ['3.7', '3.8', '3.9', '3.10', 'pypy-3.8']
1515

1616
steps:
1717
- uses: actions/checkout@v2

MANIFEST.in

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
include LICENSE
22
include mypy.ini
33
include requirements-*.txt
4-
include tox.ini

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ the type of data you'd like to stream.
183183
Features
184184
========
185185

186-
* Python >= 3.6 support
186+
* Python >= 3.7 support
187187
* An ORM-like interface with query and scan filters
188188
* Compatible with DynamoDB Local
189189
* Supports the entire DynamoDB API

docs/release_notes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Release Notes
22
=============
33

4+
Unreleased
5+
----------
6+
* Python 3.6 is no longer supported.
7+
8+
49
v5.2.0
510
----------
611
* The ``IndexMeta`` class has been removed. Now ``type(Index) == type`` (#998)

examples/attributes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class Meta:
5353

5454

5555
instance = CustomAttributeModel()
56-
instance.obj = Color('red')
56+
instance.obj = Color('red') # type: ignore
5757
instance.id = 'red'
5858
instance.save()
5959

examples/indexes.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ class Meta:
4141
TestModel.create_table(read_capacity_units=1, write_capacity_units=1, wait=True)
4242

4343
# Create an item
44-
item = TestModel('forum-example', 'thread-example')
45-
item.view = 1
46-
item.save()
44+
test_item = TestModel('forum-example', 'thread-example')
45+
test_item.view = 1
46+
test_item.save()
4747

4848
# Indexes can be queried easily using the index's hash key
49-
for item in TestModel.view_index.query(1):
50-
print("Item queried from index: {0}".format(item))
49+
for test_item in TestModel.view_index.query(1):
50+
print("Item queried from index: {0}".format(test_item))
5151

5252

5353
class GamePlayerOpponentIndex(LocalSecondaryIndex):

examples/model.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import logging
77
from pynamodb.models import Model
88
from pynamodb.attributes import (
9-
UnicodeAttribute, NumberAttribute, UnicodeSetAttribute, UTCDateTimeAttribute
9+
ListAttribute, UnicodeAttribute, NumberAttribute, UnicodeSetAttribute, UTCDateTimeAttribute
1010
)
1111
from datetime import datetime
1212

@@ -29,7 +29,7 @@ class Meta:
2929
answered = NumberAttribute(default=0)
3030
tags = UnicodeSetAttribute()
3131
last_post_datetime = UTCDateTimeAttribute(null=True)
32-
notes = ListAttribute(default=list)
32+
notes = ListAttribute(default=list) # type: ignore # todo: add ability for basic list types
3333

3434

3535
# Delete the table
@@ -60,7 +60,7 @@ class Meta:
6060
threads = []
6161
for x in range(100):
6262
thread = Thread('forum-{0}'.format(x), 'subject-{0}'.format(x))
63-
thread.tags = ['tag1', 'tag2']
63+
thread.tags = {'tag1', 'tag2'}
6464
thread.last_post_datetime = datetime.now()
6565
threads.append(thread)
6666

@@ -106,41 +106,41 @@ class Meta:
106106
if not AliasedModel.exists():
107107
AliasedModel.create_table(read_capacity_units=1, write_capacity_units=1, wait=True)
108108

109-
# Create a thread
110-
thread_item = AliasedModel(
109+
# Create an aliased model
110+
aliased_item = AliasedModel(
111111
'Some Forum',
112112
'Some Subject',
113113
tags=['foo', 'bar'],
114114
last_post_datetime=datetime.now()
115115
)
116116

117-
# Save the thread
118-
thread_item.save()
117+
# Save the aliased model
118+
aliased_item.save()
119119

120120
# Batch write operation
121121
with AliasedModel.batch_write() as batch:
122-
threads = []
122+
aliased_items = []
123123
for x in range(100):
124-
thread = AliasedModel('forum-{0}'.format(x), 'subject-{0}'.format(x))
125-
thread.tags = ['tag1', 'tag2']
126-
thread.last_post_datetime = datetime.now()
127-
threads.append(thread)
124+
aliased_item = AliasedModel('forum-{0}'.format(x), 'subject-{0}'.format(x))
125+
aliased_item.tags = {'tag1', 'tag2'}
126+
aliased_item.last_post_datetime = datetime.now()
127+
aliased_items.append(aliased_item)
128128

129-
for thread in threads:
130-
batch.save(thread)
129+
for aliased_item in aliased_items:
130+
batch.save(aliased_item)
131131

132132
# Batch get
133133
item_keys = [('forum-{0}'.format(x), 'subject-{0}'.format(x)) for x in range(100)]
134-
for item in AliasedModel.batch_get(item_keys):
135-
print("Batch get item: {0}".format(item))
134+
for aliased_item in AliasedModel.batch_get(item_keys):
135+
print("Batch get item: {0}".format(aliased_item))
136136

137137
# Scan
138-
for item in AliasedModel.scan():
139-
print("Scanned item: {0}".format(item))
138+
for aliased_item in AliasedModel.scan():
139+
print("Scanned item: {0}".format(aliased_item))
140140

141141
# Query
142-
for item in AliasedModel.query('forum-1', AliasedModel.subject.startswith('subject')):
143-
print("Query using aliased attribute: {0}".format(item))
142+
for aliased_item in AliasedModel.query('forum-1', AliasedModel.subject.startswith('subject')):
143+
print("Query using aliased attribute: {0}".format(aliased_item))
144144

145145
# Query with filters
146146
for item in Thread.query('forum-1', (Thread.views == 0) | (Thread.replies == 0)):

examples/optimistic_locking.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def assert_condition_check_fails():
4646
except TransactWriteError as e:
4747
assert isinstance(e.cause, ClientError)
4848
assert e.cause_response_code == "TransactionCanceledException"
49+
assert e.cause_response_message is not None
4950
assert "ConditionalCheckFailed" in e.cause_response_message
5051
else:
5152
raise AssertionError("The version attribute conditional check should have failed.")

mypy.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ warn_incomplete_stub = True
1010
follow_imports = normal
1111
show_error_codes = True
1212

13+
# Ignore errors in the docs/conf.py file
14+
[mypy-conf]
15+
ignore_errors = True
16+
1317
# TODO: burn these down
1418
[mypy-tests.*]
1519
ignore_errors = True

pynamodb/_compat.py

Lines changed: 0 additions & 6 deletions
This file was deleted.

pynamodb/attributes.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from typing import Any, Callable, Dict, Generic, List, Mapping, Optional, TypeVar, Type, Union, Set, overload, Iterable
1717
from typing import TYPE_CHECKING
1818

19-
from pynamodb._compat import GenericMeta
2019
from pynamodb.constants import BINARY
2120
from pynamodb.constants import BINARY_SET
2221
from pynamodb.constants import BOOLEAN
@@ -229,7 +228,7 @@ def delete(self, *values: Any) -> 'DeleteAction':
229228
return Path(self).delete(*values)
230229

231230

232-
class AttributeContainerMeta(GenericMeta):
231+
class AttributeContainerMeta(type):
233232

234233
def __new__(cls, name, bases, namespace, discriminator=None):
235234
# Defined so that the discriminator can be set in the class definition.

pynamodb/indexes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class Index(Generic[_M]):
3030

3131
@classmethod
3232
def __init_subclass__(cls, **kwargs):
33-
super().__init_subclass__(**kwargs) # type: ignore # see https://github.com/python/mypy/issues/4660
33+
super().__init_subclass__(**kwargs)
3434
if cls.Meta is not None:
3535
cls.Meta.attributes = {}
3636
for name, attribute in getmembers(cls, lambda o: isinstance(o, Attribute)):

pynamodb/settings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
def _load_module(name, path):
2525
# https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
2626
spec = importlib.util.spec_from_file_location(name, path)
27-
module = importlib.util.module_from_spec(spec)
27+
module = importlib.util.module_from_spec(spec) # type: ignore
2828
spec.loader.exec_module(module) # type: ignore
2929
return module
3030

requirements-dev.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ pytest-mock
44

55
# only used in CI
66
coveralls
7-
mypy==0.770;python_version>="3.7"
7+
mypy==0.930
88
pytest-cov

setup.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
setup(
1010
name='pynamodb',
1111
version=__import__('pynamodb').__version__,
12-
packages=find_packages(exclude=('tests', 'tests.integration',)),
12+
packages=find_packages(exclude=('examples', 'tests', 'tests.integration',)),
1313
url='http://jlafon.io/pynamodb.html',
1414
author='Jharrod LaFon',
1515
author_email='[email protected]',
@@ -18,16 +18,17 @@
1818
zip_safe=False,
1919
license='MIT',
2020
keywords='python dynamodb amazon',
21-
python_requires=">=3.6",
21+
python_requires=">=3.7",
2222
install_requires=install_requires,
2323
classifiers=[
2424
'Development Status :: 5 - Production/Stable',
2525
'Intended Audience :: Developers',
2626
'Programming Language :: Python',
2727
'Operating System :: OS Independent',
28-
'Programming Language :: Python :: 3.6',
2928
'Programming Language :: Python :: 3.7',
3029
'Programming Language :: Python :: 3.8',
30+
'Programming Language :: Python :: 3.9',
31+
'Programming Language :: Python :: 3.10',
3132
'License :: OSI Approved :: MIT License',
3233
],
3334
extras_require={

tests/conftest.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
@pytest.fixture
55
def assert_mypy_output(pytestconfig):
6-
pytest.importorskip('mypy') # we only install mypy in python>=3.6 tests
76
pytest.register_assert_rewrite('tests.mypy_helpers')
87

98
from tests.mypy_helpers import assert_mypy_output

tests/test_mypy.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
Note: The expected error strings may change in a future version of mypy.
33
Please update as needed.
44
"""
5+
import platform
6+
import pytest
7+
8+
9+
pytestmark = pytest.mark.skipif(platform.python_implementation() != "CPython", reason="tests take > 5 minutes to run on pypy")
510

611

712
def test_model(assert_mypy_output):
@@ -12,7 +17,7 @@ def test_model(assert_mypy_output):
1217
class MyModel(Model):
1318
pass
1419
15-
reveal_type(MyModel.count('hash', Path('a').between(1, 3))) # N: Revealed type is 'builtins.int'
20+
reveal_type(MyModel.count('hash', Path('a').between(1, 3))) # N: Revealed type is "builtins.int"
1621
""")
1722

1823

@@ -42,9 +47,9 @@ class MyModel(Model):
4247
4348
result_iterator = MyModel.query(123)
4449
for model in result_iterator:
45-
reveal_type(model) # N: Revealed type is '__main__.MyModel*'
50+
reveal_type(model) # N: Revealed type is "__main__.MyModel*"
4651
if result_iterator.last_evaluated_key:
47-
reveal_type(result_iterator.last_evaluated_key['my_attr']) # N: Revealed type is 'builtins.dict*[builtins.str, Any]'
52+
reveal_type(result_iterator.last_evaluated_key['my_attr']) # N: Revealed type is "builtins.dict*[builtins.str, Any]"
4853
""")
4954

5055

@@ -76,8 +81,8 @@ def test_number_attribute(assert_mypy_output):
7681
class MyModel(Model):
7782
my_attr = NumberAttribute()
7883
79-
reveal_type(MyModel.my_attr) # N: Revealed type is 'pynamodb.attributes.NumberAttribute'
80-
reveal_type(MyModel().my_attr) # N: Revealed type is 'builtins.float*'
84+
reveal_type(MyModel.my_attr) # N: Revealed type is "pynamodb.attributes.NumberAttribute"
85+
reveal_type(MyModel().my_attr) # N: Revealed type is "builtins.float*"
8186
""")
8287

8388

@@ -89,8 +94,8 @@ def test_unicode_attribute(assert_mypy_output):
8994
class MyModel(Model):
9095
my_attr = UnicodeAttribute()
9196
92-
reveal_type(MyModel.my_attr) # N: Revealed type is 'pynamodb.attributes.UnicodeAttribute'
93-
reveal_type(MyModel().my_attr) # N: Revealed type is 'builtins.str*'
97+
reveal_type(MyModel.my_attr) # N: Revealed type is "pynamodb.attributes.UnicodeAttribute"
98+
reveal_type(MyModel().my_attr) # N: Revealed type is "builtins.str*"
9499
""")
95100

96101

@@ -108,18 +113,18 @@ class MyMap(MapAttribute):
108113
class MyModel(Model):
109114
m1 = MyMap()
110115
111-
reveal_type(MyModel.m1) # N: Revealed type is '__main__.MyMap'
112-
reveal_type(MyModel().m1) # N: Revealed type is '__main__.MyMap'
113-
reveal_type(MyModel.m1.m2) # N: Revealed type is '__main__.MySubMap'
114-
reveal_type(MyModel().m1.m2) # N: Revealed type is '__main__.MySubMap'
115-
reveal_type(MyModel.m1.m2.s) # N: Revealed type is 'builtins.str*'
116-
reveal_type(MyModel().m1.m2.s) # N: Revealed type is 'builtins.str*'
116+
reveal_type(MyModel.m1) # N: Revealed type is "__main__.MyMap"
117+
reveal_type(MyModel().m1) # N: Revealed type is "__main__.MyMap"
118+
reveal_type(MyModel.m1.m2) # N: Revealed type is "__main__.MySubMap"
119+
reveal_type(MyModel().m1.m2) # N: Revealed type is "__main__.MySubMap"
120+
reveal_type(MyModel.m1.m2.s) # N: Revealed type is "builtins.str*"
121+
reveal_type(MyModel().m1.m2.s) # N: Revealed type is "builtins.str*"
117122
118-
reveal_type(MyMap.m2) # N: Revealed type is '__main__.MySubMap'
119-
reveal_type(MyMap().m2) # N: Revealed type is '__main__.MySubMap'
123+
reveal_type(MyMap.m2) # N: Revealed type is "__main__.MySubMap"
124+
reveal_type(MyMap().m2) # N: Revealed type is "__main__.MySubMap"
120125
121-
reveal_type(MySubMap.s) # N: Revealed type is 'pynamodb.attributes.UnicodeAttribute'
122-
reveal_type(MySubMap().s) # N: Revealed type is 'builtins.str*'
126+
reveal_type(MySubMap.s) # N: Revealed type is "pynamodb.attributes.UnicodeAttribute"
127+
reveal_type(MySubMap().s) # N: Revealed type is "builtins.str*"
123128
""")
124129

125130

@@ -133,14 +138,14 @@ class MyMap(MapAttribute):
133138
134139
class MyModel(Model):
135140
my_list = ListAttribute(of=MyMap)
136-
my_untyped_list = ListAttribute() # E: Need type annotation for 'my_untyped_list' [var-annotated]
141+
my_untyped_list = ListAttribute() # E: Need type annotation for "my_untyped_list" [var-annotated]
137142
138-
reveal_type(MyModel.my_list) # N: Revealed type is 'pynamodb.attributes.ListAttribute[__main__.MyMap]'
139-
reveal_type(MyModel().my_list) # N: Revealed type is 'builtins.list*[__main__.MyMap*]'
140-
reveal_type(MyModel().my_list[0].my_sub_attr) # N: Revealed type is 'builtins.str*'
143+
reveal_type(MyModel.my_list) # N: Revealed type is "pynamodb.attributes.ListAttribute[__main__.MyMap]"
144+
reveal_type(MyModel().my_list) # N: Revealed type is "builtins.list*[__main__.MyMap*]"
145+
reveal_type(MyModel().my_list[0].my_sub_attr) # N: Revealed type is "builtins.str*"
141146
142147
# Untyped lists are not well supported yet
143-
reveal_type(MyModel().my_untyped_list[0].my_sub_attr) # N: Revealed type is 'Any'
148+
reveal_type(MyModel().my_untyped_list[0].my_sub_attr) # N: Revealed type is "Any"
144149
""")
145150

146151

@@ -156,11 +161,11 @@ class MyModel(Model):
156161
my_list = ListAttribute(of=MyMap)
157162
my_map = MyMap()
158163
159-
reveal_type(MyModel.my_list[0]) # N: Revealed type is 'pynamodb.expressions.operand.Path'
160-
reveal_type(MyModel.my_list[0] == MyModel()) # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
164+
reveal_type(MyModel.my_list[0]) # N: Revealed type is "pynamodb.expressions.operand.Path"
165+
reveal_type(MyModel.my_list[0] == MyModel()) # N: Revealed type is "pynamodb.expressions.condition.Comparison"
161166
# the following string indexing is not type checked - not by mypy nor in runtime
162-
reveal_type(MyModel.my_list[0]['my_sub_attr'] == 'foobar') # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
163-
reveal_type(MyModel.my_map == 'foobar') # N: Revealed type is 'pynamodb.expressions.condition.Comparison'
167+
reveal_type(MyModel.my_list[0]['my_sub_attr'] == 'foobar') # N: Revealed type is "pynamodb.expressions.condition.Comparison"
168+
reveal_type(MyModel.my_map == 'foobar') # N: Revealed type is "pynamodb.expressions.condition.Comparison"
164169
""")
165170

166171

@@ -247,5 +252,5 @@ def test_transactions(assert_mypy_output):
247252
assert_mypy_output("""
248253
from pynamodb.transactions import TransactWrite
249254
with TransactWrite() as tx:
250-
reveal_type(tx) # N: Revealed type is 'pynamodb.transactions.TransactWrite*'
255+
reveal_type(tx) # N: Revealed type is "pynamodb.transactions.TransactWrite*"
251256
""")

tox.ini

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)