Skip to content

Commit

Permalink
Immutable locking config (#189)
Browse files Browse the repository at this point in the history
* Immutable locking config

* Fixed behavior with disabled config locking

* Instead of opt-out immutable config opt-in with deprecation warnings

* Added copy method on PNConfiguration instance
  • Loading branch information
seba-aln authored Aug 8, 2024
1 parent e5416cf commit b56d703
Show file tree
Hide file tree
Showing 12 changed files with 662 additions and 160 deletions.
28 changes: 28 additions & 0 deletions pubnub/pnconfiguration.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import warnings
from typing import Any
from copy import deepcopy
from Cryptodome.Cipher import AES
from pubnub.enums import PNHeartbeatNotificationOptions, PNReconnectionPolicy
from pubnub.exceptions import PubNubException
Expand All @@ -12,6 +15,7 @@ class PNConfiguration(object):
RECONNECTION_MIN_EXPONENTIAL_BACKOFF = 1
RECONNECTION_MAX_EXPONENTIAL_BACKOFF = 32
DEFAULT_CRYPTO_MODULE = LegacyCryptoModule
_locked = False

def __init__(self):
# TODO: add validation
Expand Down Expand Up @@ -48,9 +52,13 @@ def __init__(self):
self.cryptor = None
self.file_cryptor = None
self._crypto_module = None
self.disable_config_locking = True
self._locked = False

def validate(self):
PNConfiguration.validate_not_empty_string(self.uuid)
if self.disable_config_locking:
warnings.warn(DeprecationWarning('Mutable config will be deprecated in the future.'))

def validate_not_empty_string(value: str):
assert value and isinstance(value, str) and value.strip() != "", "UUID missing or invalid type"
Expand Down Expand Up @@ -168,3 +176,23 @@ def user_id(self):
def user_id(self, user_id):
PNConfiguration.validate_not_empty_string(user_id)
self._uuid = user_id

def lock(self):
self.__dict__['_locked'] = False if self.disable_config_locking else True

def copy(self):
config_copy = deepcopy(self)
config_copy.__dict__['_locked'] = False
return config_copy

def __setattr__(self, name: str, value: Any) -> None:
if self._locked:
warnings.warn(UserWarning('Configuration is locked. Any changes made won\'t have any effect'))
return
if name in ['uuid', 'user_id']:
PNConfiguration.validate_not_empty_string(value)
self.__dict__['_uuid'] = value
elif name in ['cipher_mode', 'fallback_cipher_mode', 'crypto_module']:
self.__dict__[f'_{name}'] = value
else:
self.__dict__[name] = value
11 changes: 9 additions & 2 deletions pubnub/pubnub_core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import time
from warnings import warn
from copy import deepcopy
from pubnub.endpoints.entities.membership.add_memberships import AddSpaceMembers, AddUserSpaces
from pubnub.endpoints.entities.membership.update_memberships import UpdateSpaceMembers, UpdateUserSpaces
from pubnub.endpoints.entities.membership.fetch_memberships import FetchSpaceMemberships, FetchUserMemberships
Expand All @@ -25,6 +26,8 @@

from abc import ABCMeta, abstractmethod

from pubnub.pnconfiguration import PNConfiguration

from .endpoints.objects_v2.uuid.set_uuid import SetUuid
from .endpoints.objects_v2.channel.get_all_channels import GetAllChannels
from .endpoints.objects_v2.channel.get_channel import GetChannel
Expand Down Expand Up @@ -98,8 +101,12 @@ class PubNubCore:

_subscription_registry: PNSubscriptionRegistry

def __init__(self, config):
self.config = config
def __init__(self, config: PNConfiguration):
if not config.disable_config_locking:
config.lock()
self.config = deepcopy(config)
else:
self.config = config
self.config.validate()
self.headers = {
'User-Agent': self.sdk_name
Expand Down
49 changes: 32 additions & 17 deletions tests/integrational/asyncio/test_change_uuid.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,33 +8,48 @@
from tests.helper import pnconf_demo_copy


@pn_vcr.use_cassette(
'tests/integrational/fixtures/asyncio/signal/uuid.yaml',
filter_query_parameters=['seqn', 'pnsdk', 'l_sig']
)
@pn_vcr.use_cassette('tests/integrational/fixtures/asyncio/signal/uuid.json',
filter_query_parameters=['seqn', 'pnsdk', 'l_sig'], serializer='pn_json')
@pytest.mark.asyncio
async def test_single_channel(event_loop):
pnconf_demo = pnconf_demo_copy()
pn = PubNubAsyncio(pnconf_demo, custom_event_loop=event_loop)
async def test_change_uuid():
with pytest.warns(UserWarning):
pnconf = pnconf_demo_copy()
pnconf.disable_config_locking = False
pn = PubNubAsyncio(pnconf)

chan = 'unique_sync'
envelope = await pn.signal().channel(chan).message('test').future()

pnconf.uuid = 'new-uuid'
envelope = await pn.signal().channel(chan).message('test').future()

assert isinstance(envelope, AsyncioEnvelope)
assert not envelope.status.is_error()
assert envelope.result.timetoken == '17224117487136760'
assert isinstance(envelope.result, PNSignalResult)
assert isinstance(envelope.status, PNStatus)


@pn_vcr.use_cassette('tests/integrational/fixtures/asyncio/signal/uuid_no_lock.json',
filter_query_parameters=['seqn', 'pnsdk', 'l_sig'], serializer='pn_json')
@pytest.mark.asyncio
async def test_change_uuid_no_lock():
pnconf = pnconf_demo_copy()
pnconf.disable_config_locking = True
pn = PubNubAsyncio(pnconf)

chan = 'unique_sync'
envelope = await pn.signal().channel(chan).message('test').future()

assert isinstance(envelope, AsyncioEnvelope)
assert not envelope.status.is_error()
assert envelope.result.timetoken == '15640051159323676'
assert isinstance(envelope.result, PNSignalResult)
assert isinstance(envelope.status, PNStatus)

pnconf_demo.uuid = 'new-uuid'
pnconf.uuid = 'new-uuid'
envelope = await pn.signal().channel(chan).message('test').future()

assert isinstance(envelope, AsyncioEnvelope)
assert not envelope.status.is_error()
assert envelope.result.timetoken == '15640051159323677'
assert envelope.result.timetoken == '17224117494275030'
assert isinstance(envelope.result, PNSignalResult)
assert isinstance(envelope.status, PNStatus)

await pn.stop()


def test_uuid_validation_at_init(event_loop):
with pytest.raises(AssertionError) as exception:
Expand Down
111 changes: 111 additions & 0 deletions tests/integrational/fixtures/asyncio/signal/uuid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
{
"version": 1,
"interactions": [
{
"request": {
"method": "GET",
"uri": "https://ps.pndsn.com/signal/demo/demo/0/unique_sync/0/%22test%22?uuid=uuid-mock",
"body": null,
"headers": {
"User-Agent": [
"PubNub-Python/8.0.0"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Accept": [
"*/*"
],
"Connection": [
"keep-alive"
]
}
},
"response": {
"status": {
"code": 200,
"message": "OK"
},
"headers": {
"Connection": [
"keep-alive"
],
"Cache-Control": [
"no-cache"
],
"Date": [
"Wed, 31 Jul 2024 07:42:28 GMT"
],
"Content-Length": [
"30"
],
"Content-Type": [
"text/javascript; charset=\"UTF-8\""
],
"Access-Control-Allow-Methods": [
"GET"
],
"Access-Control-Allow-Origin": [
"*"
]
},
"body": {
"string": "[1,\"Sent\",\"17224117484567462\"]"
}
}
},
{
"request": {
"method": "GET",
"uri": "https://ps.pndsn.com/signal/demo/demo/0/unique_sync/0/%22test%22?uuid=uuid-mock",
"body": null,
"headers": {
"User-Agent": [
"PubNub-Python/8.0.0"
],
"Accept-Encoding": [
"gzip, deflate"
],
"Accept": [
"*/*"
],
"Connection": [
"keep-alive"
]
}
},
"response": {
"status": {
"code": 200,
"message": "OK"
},
"headers": {
"Connection": [
"keep-alive"
],
"Cache-Control": [
"no-cache"
],
"Date": [
"Wed, 31 Jul 2024 07:42:28 GMT"
],
"Content-Length": [
"30"
],
"Content-Type": [
"text/javascript; charset=\"UTF-8\""
],
"Access-Control-Allow-Methods": [
"GET"
],
"Access-Control-Allow-Origin": [
"*"
]
},
"body": {
"string": "[1,\"Sent\",\"17224117487136760\"]"
}
}
}
]
}
62 changes: 0 additions & 62 deletions tests/integrational/fixtures/asyncio/signal/uuid.yaml

This file was deleted.

Loading

0 comments on commit b56d703

Please sign in to comment.