Skip to content

Commit

Permalink
Merge branch 'master' into test-py312
Browse files Browse the repository at this point in the history
  • Loading branch information
ikonst authored May 25, 2024
2 parents 0f0c6b0 + 0a4ce93 commit 5f6bf22
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 23 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ on:
jobs:
deploy:
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- uses: actions/checkout@v3
- name: Set up Python
Expand All @@ -30,12 +33,9 @@ jobs:
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: ${{ github.event_name == 'release' }}
with:
password: ${{ secrets.PYPI_API_TOKEN }}

- name: Publish to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
if: ${{ github.event_name == 'workflow_dispatch' }}
with:
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
90 changes: 73 additions & 17 deletions docs/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,83 @@
Release Notes
=============

Unreleased
----------
v6.0.0
------

This is a major release and contains breaking changes. Please read the notes below carefully.

Breaking changes:

* :py:class:`~pynamodb.attributes.BinaryAttribute` and :py:class:`~pynamodb.attributes.BinarySetAttribute` have undergone breaking changes:

* The attributes' internal encoding has changed. To prevent this change going unnoticed, :code:`legacy_encoding` have been made required: see :doc:`upgrading_binary` for details.
* The attributes' internal encoding has changed. To prevent this change going unnoticed, a new required :code:`legacy_encoding` parameter was added: see :doc:`upgrading_binary` for details.
If your codebase uses :py:class:`~pynamodb.attributes.BinaryAttribute` or :py:class:`~pynamodb.attributes.BinarySetAttribute`,
go over the attribute declarations and mark them accordingly.
* When using binary attributes, the return value of :meth:`~pynamodb.models.Model.serialize` will no longer be JSON-serializable
since it will contain :code:`bytes` objects. Use :meth:`~pynamodb.models.Model.to_dynamodb_dict`
since it will contain :code:`bytes` objects. Use :meth:`~pynamodb.attributes.AttributeContainer.to_dynamodb_dict` and :meth:`~pynamodb.attributes.AttributeContainer.to_simple_dict` for JSON-serializable mappings.
for a safe JSON-serializable representation.

* Python 3.6 is no longer supported.
* :meth:`Index.count <pynamodb.indexes.Index.count>`, :meth:`Index.query <pynamodb.indexes.Index.query>`,
and :meth:`Indexn.scan <pynamodb.indexes.Index.scan>` are now instance methods.
* PynamoDB no longer has a default AWS region (used to be us-east-1) (:pr:`1003`).
If needed, update your models' `Meta` or set the `AWS_DEFAULT_REGION` environment variable.
* :py:class:`~pynamodb.models.Model`'s JSON serialization helpers were changed:

Other changes in this release:
* :code:`to_json` was renamed to :meth:`~pynamodb.attributes.AttributeContainer.to_simple_dict` (:pr:`1126`). Additionally, :meth:`~pynamodb.attributes.AttributeContainer.to_dynamodb_dict`
and :meth:`~pynamodb.attributes.AttributeContainer.from_dynamodb_dict` were added for round-trip JSON serialization.
* :code:`pynamodb.util.attribute_value_to_json` was removed (:pr:`1126`)

* :py:class:`~pynamodb.attributes.Attribute`'s :code:`default` parameter must be either an immutable value
(of one of the built-in immutable types) or a callable.
This prevents a common class of errors caused by unintentionally mutating the default value.
A simple workaround is to pass an initializer (e.g. change :code:`default={}` to
:code:`default=dict`) or wrap in a lambda (e.g. change :code:`default={'foo': 'bar'}` to
:code:`default=lambda: {'foo': 'bar'}`).
* :meth:`~pynamodb.indexes.Index.count`, :meth:`~pynamodb.indexes.Index.query`,
and :meth:`~pynamodb.indexes.Index.scan` are now instance methods.
* :py:class:`~pynamodb.settings.OperationSettings` has been removed.

Major changes:

* We are now compatible with `opentelemetry botocore instrumentation <https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation/opentelemetry-instrumentation-botocore>`_.
* We've reduced our usage of botocore private APIs (:pr:`1079`). On multiple occasions, new versions
of botocore broke PynamoDB, and this change lessens the likelihood of that happening in the future
by reducing (albeit not eliminating) our reliance on private botocore APIs.

Minor changes:

* :meth:`~pynamodb.models.Model.save`, :meth:`~pynamodb.models.Model.update`, :meth:`~pynamodb.models.Model.delete_item`,
and :meth:`~pynamodb.models.Model.delete` now accept a ``add_version_condition`` parameter.
See :ref:`optimistic_locking_version_condition` for more details.
* :meth:`~pynamodb.models.Model.batch_get`, has guard rails defending against items without a hash_key and range_key.
* :meth:`~pynamodb.attributes.Attribute.set`, can remove attribute by assigning an empty value in the update expression.

v5.5.1
----------
* Fix compatibility with botocore 1.33.2 (#1205)

v5.5.0
----------
* :meth:`~pynamodb.models.Model.save`, :meth:`~pynamodb.models.Model.update`, :meth:`~pynamodb.models.Model.delete_item`,
and :meth:`~pynamodb.models.Model.delete` now accept a ``add_version_condition`` parameter.
See :ref:`optimistic_locking_version_condition` for more details.

v5.4.1
----------
* Use model's AWS credentials in threads (#1164)

A model can specify custom AWS credentials in the ``Meta`` class (in lieu of "global"
AWS credentials from the environment). Previously those model-specific credentials
were not used from within new threads.

Contributors to this release:

* @atsuoishimoto

v5.4.0
----------
* Expose transaction cancellation reasons in
:meth:`~pynamodb.exceptions.TransactWriteError.cancellation_reasons` and
:meth:`~pynamodb.exceptions.TransactGetError.cancellation_reasons` (#1144).

v5.3.2
----------
Expand Down Expand Up @@ -135,8 +188,7 @@ v5.0.0

This is major release and contains breaking changes. Please read the notes below carefully.

Breaking changes
================
Breaking changes:

* Python 2 is no longer supported. Python 3.6 or greater is now required.
* :py:class:`~pynamodb.attributes.UnicodeAttribute` and :py:class:`~pynamodb.attributes.BinaryAttribute` now support empty values (:pr:`830`)
Expand All @@ -154,18 +206,17 @@ Breaking changes
* Remove ``pynamodb.connection.util.pythonic`` (:pr:`753`) and (:pr:`865`)
* Remove ``ModelContextManager`` class (:pr:`861`)

Features
========
Features:

**Polymorphism**
* **Polymorphism**

This release introduces :ref:`polymorphism` support via :py:class:`DiscriminatorAttribute <pynamodb.attributes.DiscriminatorAttribute>`.
Discriminator values are written to DynamoDB and used during deserialization to instantiate the desired class.
This release introduces :ref:`polymorphism` support via :py:class:`DiscriminatorAttribute <pynamodb.attributes.DiscriminatorAttribute>`.
Discriminator values are written to DynamoDB and used during deserialization to instantiate the desired class.

**Model Serialization**
* **Model Serialization**

THe ``Model`` class now includes public methods for serializing and deserializing its attributes.
``Model.serialize`` and ``Model.deserialize`` convert the model to/from a dictionary of DynamoDB attribute values.
The ``Model`` class now includes public methods for serializing and deserializing its attributes.
``Model.serialize`` and ``Model.deserialize`` convert the model to/from a dictionary of DynamoDB attribute values.

Other changes in this release:

Expand All @@ -182,6 +233,11 @@ Contributors to this release:
* :user:`rchilaka`-amzn
* :user:`jonathantan`

v4.4.0
----------
* Update for botocore 1.28 private API change (#1130) which caused the following exception::

TypeError: _convert_to_request_dict() missing 1 required positional argument: 'endpoint_url'

v4.3.3
----------
Expand Down
5 changes: 4 additions & 1 deletion docs/transaction.rst
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ Now, say you make another attempt to debit one of the accounts when they don't h
condition=(
(BankStatement.account_balance >= transfer_amount) &
(BankStatement.is_active == True)
)
),
return_values=ALL_OLD
)
transaction.update(
BankStatement(user_id='user2'),
Expand All @@ -107,6 +108,8 @@ Now, say you make another attempt to debit one of the accounts when they don't h
assert e.cause_response_code == 'TransactionCanceledException'
# the first 'update' was a reason for the cancellation
assert e.cancellation_reasons[0].code == 'ConditionalCheckFailed'
# when return_values=ALL_OLD, the old values can be accessed from the raw_item property
assert BankStatement.from_dynamodb_dict(e.cancellation_reasons[0].raw_item) == user1_statement
# the second 'update' wasn't a reason, but was cancelled too
assert e.cancellation_reasons[1] is None
Expand Down
2 changes: 1 addition & 1 deletion pynamodb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
"""
__author__ = 'Jharrod LaFon'
__license__ = 'MIT'
__version__ = '6.0a0'
__version__ = '6.0.0'
2 changes: 1 addition & 1 deletion pynamodb/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ class MapAttribute(Attribute[Mapping[_KT, _VT]], AttributeContainer, metaclass=M
For example, below we define "MyModel" which contains a MapAttribute "my_map":
class MyModel(Model):
my_map = MapAttribute(attr_name="dynamo_name", default={})
my_map = MapAttribute(attr_name="dynamo_name", default=dict)
When instantiated in this manner (as a class attribute of an AttributeContainer class), the MapAttribute
class acts as an instance of the Attribute class. The instance stores data about the attribute (in this
Expand Down
1 change: 1 addition & 0 deletions pynamodb/connection/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ def _make_api_call(self, operation_name: str, operation_kwargs: Dict) -> Dict:
CancellationReason(
code=d['Code'],
message=d.get('Message'),
raw_item=cast(Optional[Dict[str, Dict[str, Any]]], d.get('Item')),
) if d['Code'] != 'None' else None
)
for d in cancellation_reasons
Expand Down
1 change: 1 addition & 0 deletions pynamodb/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ class CancellationReason:
"""
code: str
message: Optional[str] = None
raw_item: Optional[Dict[str, Dict[str, Any]]] = None


class TransactWriteError(PynamoDBException):
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/test_transaction_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import pytest

from pynamodb.connection import Connection
from pynamodb.constants import ALL_OLD
from pynamodb.exceptions import CancellationReason
from pynamodb.exceptions import DoesNotExist, TransactWriteError, InvalidStateError

Expand Down Expand Up @@ -168,6 +169,22 @@ def test_transact_write__error__transaction_cancelled__condition_check_failure(c
assert BankStatement.Meta.table_name in exc_info.value.cause.MSG_TEMPLATE


@pytest.mark.ddblocal
def test_transact_write__error__transaction_cancelled__condition_check_failure__return_all_old(connection):
# create a users and a bank statements for them
User(1).save()

# attempt to do this as a transaction with the condition that they don't already exist
with pytest.raises(TransactWriteError) as exc_info:
with TransactWrite(connection=connection) as transaction:
transaction.save(User(1), condition=(User.user_id.does_not_exist()), return_values=ALL_OLD)
assert exc_info.value.cause_response_code == TRANSACTION_CANCELLED
assert 'ConditionalCheckFailed' in exc_info.value.cause_response_message
assert exc_info.value.cancellation_reasons == [
CancellationReason(code='ConditionalCheckFailed', message='The conditional request failed', raw_item=User(1).to_dynamodb_dict()),
]


@pytest.mark.ddblocal
def test_transact_write__error__transaction_cancelled__partial_failure(connection):
User(2).delete()
Expand Down

0 comments on commit 5f6bf22

Please sign in to comment.