Skip to content

Commit ecb16f4

Browse files
Add option to set If-Match eTag for objects write requests (#205)
* Add option to set `If-Match` eTag for objects write requests * Added tests and some fixes * examples adjustment * How to get http status in example * PubNub SDK 10.2.0 release. --------- Co-authored-by: PubNub Release Bot <[email protected]>
1 parent b9ab0a9 commit ecb16f4

25 files changed

+2600
-8
lines changed

.pubnub.yml

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: python
2-
version: 10.1.0
2+
version: 10.2.0
33
schema: 1
44
scm: github.com/pubnub/python
55
sdks:
@@ -18,7 +18,7 @@ sdks:
1818
distributions:
1919
- distribution-type: library
2020
distribution-repository: package
21-
package-name: pubnub-10.1.0
21+
package-name: pubnub-10.2.0
2222
location: https://pypi.org/project/pubnub/
2323
supported-platforms:
2424
supported-operating-systems:
@@ -91,8 +91,8 @@ sdks:
9191
-
9292
distribution-type: library
9393
distribution-repository: git release
94-
package-name: pubnub-10.1.0
95-
location: https://github.com/pubnub/python/releases/download/10.1.0/pubnub-10.1.0.tar.gz
94+
package-name: pubnub-10.2.0
95+
location: https://github.com/pubnub/python/releases/download/10.2.0/pubnub-10.2.0.tar.gz
9696
supported-platforms:
9797
supported-operating-systems:
9898
Linux:
@@ -163,6 +163,11 @@ sdks:
163163
license-url: https://github.com/encode/httpx/blob/master/LICENSE.md
164164
is-required: Required
165165
changelog:
166+
- date: 2025-02-07
167+
version: 10.2.0
168+
changes:
169+
- type: feature
170+
text: "Write protection with `If-Match` eTag header for setting channel and uuid metadata."
166171
- date: 2025-01-30
167172
version: 10.1.0
168173
changes:

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 10.2.0
2+
February 07 2025
3+
4+
#### Added
5+
- Write protection with `If-Match` eTag header for setting channel and uuid metadata.
6+
17
## 10.1.0
28
January 30 2025
39

examples/native_sync/using_etag.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import os
2+
3+
from copy import deepcopy
4+
from pubnub.pubnub import PubNub
5+
from pubnub.pnconfiguration import PNConfiguration
6+
from pubnub.exceptions import PubNubException
7+
8+
config = PNConfiguration()
9+
config.publish_key = os.getenv('PUBLISH_KEY', default='demo')
10+
config.subscribe_key = os.getenv('SUBSCRIBE_KEY', default='demo')
11+
config.user_id = "example"
12+
13+
config_2 = deepcopy(config)
14+
config_2.user_id = "example_2"
15+
16+
pubnub = PubNub(config)
17+
pubnub_2 = PubNub(config_2)
18+
19+
sample_user = {
20+
"uuid": "SampleUser",
21+
"name": "John Doe",
22+
"email": "[email protected]",
23+
"custom": {"age": 42, "address": "123 Main St."},
24+
}
25+
26+
# One client creates a metada for the user "SampleUser" and successfully writes it to the server.
27+
set_result = pubnub.set_uuid_metadata(
28+
**sample_user,
29+
include_custom=True,
30+
include_status=True,
31+
include_type=True
32+
).sync()
33+
34+
# We store the eTag for the user for further updates.
35+
original_e_tag = set_result.result.data.get('eTag')
36+
37+
# Another client sets the user meta with the same UUID but different data.
38+
overwrite_result = pubnub_2.set_uuid_metadata(uuid="SampleUser", name="Jane Doe").sync()
39+
new_e_tag = overwrite_result.result.data.get('eTag')
40+
41+
# We can verify that there is a new eTag for the user.
42+
print(f"{original_e_tag == new_e_tag=}")
43+
44+
# We modify the user and try to update it.
45+
updated_user = {**sample_user, "custom": {"age": 43, "address": "321 Other St."}}
46+
47+
try:
48+
update_result = pubnub.set_uuid_metadata(
49+
**updated_user,
50+
include_custom=True,
51+
include_status=True,
52+
include_type=True
53+
).if_matches_etag(original_e_tag).sync()
54+
except PubNubException as e:
55+
# We get an exception and after reading the error message we can see that the reason is that the eTag is outdated.
56+
print(f"Update failed: {e.get_error_message().get('message')}\nHTTP Status Code: {e.get_status_code()}")
57+
58+
59+
except Exception as e:
60+
print(f"Unexpected error: {e}")

pubnub/endpoints/endpoint.py

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ class Endpoint(object):
2525

2626
__metaclass__ = ABCMeta
2727
_path = None
28+
_custom_headers: dict = None
2829

2930
def __init__(self, pubnub):
3031
self.pubnub = pubnub
@@ -100,6 +101,9 @@ def request_headers(self):
100101
if self.http_method() in [HttpMethod.POST, HttpMethod.PATCH]:
101102
headers["Content-type"] = "application/json"
102103

104+
if self._custom_headers:
105+
headers.update(self._custom_headers)
106+
103107
return headers
104108

105109
def build_file_upload_request(self):

pubnub/endpoints/objects_v2/objects_endpoint.py

+9
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ class ObjectsEndpoint(Endpoint):
1717

1818
_includes: PNIncludes = None
1919

20+
__if_matches_etag: str = None
21+
22+
_custom_headers: dict = {}
23+
2024
def __init__(self, pubnub):
2125
Endpoint.__init__(self, pubnub)
2226

@@ -36,6 +40,11 @@ def validate_params(self):
3640
def validate_specific_params(self):
3741
pass
3842

43+
def if_matches_etag(self, etag: str):
44+
self.__if_matches_etag = etag
45+
self._custom_headers.update({"If-Match": etag})
46+
return self
47+
3948
def encoded_params(self):
4049
params = {}
4150
if isinstance(self, ListEndpoint):

pubnub/exceptions.py

+13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from json import loads, JSONDecodeError
2+
3+
14
class PubNubException(Exception):
25
def __init__(self, errormsg="", status_code=0, pn_error=None, status=None):
36
self._errormsg = errormsg
@@ -19,6 +22,16 @@ def _status(self):
1922
raise DeprecationWarning
2023
return self.status
2124

25+
def get_status_code(self):
26+
return self._status_code
27+
28+
def get_error_message(self):
29+
try:
30+
error = loads(self._errormsg)
31+
return error.get('error')
32+
except JSONDecodeError:
33+
return self._errormsg
34+
2235

2336
class PubNubAsyncioException(Exception):
2437
def __init__(self, result, status):

pubnub/pubnub_core.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696

9797
class PubNubCore:
9898
"""A base class for PubNub Python API implementations"""
99-
SDK_VERSION = "10.1.0"
99+
SDK_VERSION = "10.2.0"
100100
SDK_NAME = "PubNub-Python"
101101

102102
TIMESTAMP_DIVIDER = 1000
@@ -288,9 +288,9 @@ def set_uuid_metadata(self, uuid: str = None, include_custom: bool = None, custo
288288
include_type=include_type, status=status, type=type, name=name, email=email,
289289
external_id=external_id, profile_url=profile_url)
290290

291-
def get_uuid_metadata(self, uuud: str = None, include_custom: bool = None, include_status: bool = True,
291+
def get_uuid_metadata(self, uuid: str = None, include_custom: bool = None, include_status: bool = True,
292292
include_type: bool = True) -> GetUuid:
293-
return GetUuid(self, uuid=uuud, include_custom=include_custom, include_status=include_status,
293+
return GetUuid(self, uuid=uuid, include_custom=include_custom, include_status=include_status,
294294
include_type=include_type)
295295

296296
def remove_uuid_metadata(self, uuid: str = None) -> RemoveUuid:

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
setup(
44
name='pubnub',
5-
version='10.1.0',
5+
version='10.2.0',
66
description='PubNub Real-time push service in the cloud',
77
author='PubNub',
88
author_email='[email protected]',

tests/integrational/asyncio/objects_v2/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)