From 00663770917492ba22470b6f220bc93a85dcf3ca Mon Sep 17 00:00:00 2001 From: Johanna Larsson Date: Fri, 5 Apr 2019 11:23:53 +0200 Subject: [PATCH] fix: Avoid dict as default parameter (#50) Fixes an issue where the default timestamp gets locked the first time it is called, and is the same for every subsequent request. --- HISTORY.md | 2 ++ castle/client.py | 16 ++++++++++++---- castle/test/client_test.py | 23 +++++++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 0231765..85dcfd0 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,7 @@ ## master +- [#50](https://github.com/castle/castle-python/pull/50) avoid dict as default parameter + ## 2.3.0 (2019-01-16) - [#48](https://github.com/castle/castle-python/pull/48) add connection pooling diff --git a/castle/client.py b/castle/client.py index 008a9ee..75db2a4 100644 --- a/castle/client.py +++ b/castle/client.py @@ -14,20 +14,26 @@ class Client(object): @classmethod - def from_request(cls, request, options={}): + def from_request(cls, request, options=None): + if options is None: + options = {} return cls( cls.to_context(request, options), cls.to_options(options) ) @staticmethod - def to_context(request, options={}): + def to_context(request, options=None): + if options is None: + options = {} default_context = ContextDefault( request, options.get('cookies')).call() return ContextMerger.call(default_context, options.get('context', {})) @staticmethod - def to_options(options={}): + def to_options(options=None): + if options is None: + options = {} options.setdefault('timestamp', generate_timestamp()) if 'traits' in options: warnings.warn('use user_traits instead of traits key', DeprecationWarning) @@ -40,7 +46,9 @@ def failover_response_or_raise(options, exception): raise exception return FailoverResponse(options.get('user_id'), None, exception.__class__.__name__).call() - def __init__(self, context, options={}): + def __init__(self, context, options=None): + if options is None: + options = {} self.do_not_track = options.get('do_not_track', False) self.timestamp = options.get('timestamp') self.context = context diff --git a/castle/test/client_test.py b/castle/test/client_test.py index d1c12d9..9f1618d 100644 --- a/castle/test/client_test.py +++ b/castle/test/client_test.py @@ -1,3 +1,4 @@ +import json from collections import namedtuple import responses from castle.test import mock, unittest @@ -215,3 +216,25 @@ def test_failover_strategy_throw(self): with self.assertRaises(Exception): Client.failover_response_or_raise(options, Exception()) configuration.failover_strategy = 'allow' + + @responses.activate + def test_timestamps_are_not_global(self): + response_text = {'action': 'allow', 'user_id': '1234'} + responses.add( + responses.POST, + 'https://api.castle.io/v1/authenticate', + json=response_text, + status=200 + ) + options1 = {'event': '$login.authenticate', 'user_id': '1234'} + options2 = {'event': '$login.authenticate', 'user_id': '1234'} + client1 = Client.from_request(request()) + client1.authenticate(options1) + self.mock_timestamp.return_value = '2018-01-02T04:04:05.678' + client2 = Client.from_request(request()) + client2.authenticate(options2) + + response_body1 = json.loads(responses.calls[0].request.body) + response_body2 = json.loads(responses.calls[1].request.body) + + self.assertNotEqual(response_body1['timestamp'], response_body2['timestamp'])