Skip to content

Commit 454a2fa

Browse files
authored
Add session-specific conf. (#264)
Currently, configuration values like user-agent and API urls are essentially global variables. This commit adds config to the BoxSession, allowing each session to override config values.
1 parent 6708e8e commit 454a2fa

File tree

10 files changed

+98
-23
lines changed

10 files changed

+98
-23
lines changed

HISTORY.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Release History
116116
- Added methods for configuring ``JWTAuth`` from config file: ``JWTAuth.from_settings_file`` and
117117
``JWTAuth.from_settings_dictionary``.
118118
- Added ``network_response`` property to ``BoxOAuthException``.
119+
- API Configuration can now be done per ``BoxSession`` instance.
119120

120121
**Other**
121122

boxsdk/auth/oauth2.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ def __init__(
109109
self._box_device_id = box_device_id
110110
self._box_device_name = box_device_name
111111
self._closed = False
112+
self._api_config = API()
112113

113114
@property
114115
def access_token(self):
@@ -132,6 +133,14 @@ def closed(self):
132133
"""
133134
return self._closed
134135

136+
@property
137+
def api_config(self):
138+
"""
139+
140+
:rtype: :class:`API`
141+
"""
142+
return self._api_config
143+
135144
def get_authorization_url(self, redirect_url):
136145
"""
137146
Get the authorization url based on the client id and the redirect url passed in
@@ -162,7 +171,7 @@ def get_authorization_url(self, redirect_url):
162171
# encode the parameters as ASCII bytes.
163172
params = [(key.encode('utf-8'), value.encode('utf-8')) for (key, value) in params]
164173
query_string = urlencode(params)
165-
return urlunsplit(('', '', API.OAUTH2_AUTHORIZE_URL, query_string, '')), csrf_token
174+
return urlunsplit(('', '', self._api_config.OAUTH2_AUTHORIZE_URL, query_string, '')), csrf_token
166175

167176
def authenticate(self, auth_code):
168177
"""
@@ -320,7 +329,7 @@ def _execute_token_request(self, data, access_token, expect_refresh_token=True):
320329
:class:`TokenResponse`
321330
"""
322331
self._check_closed()
323-
url = '{base_auth_url}/token'.format(base_auth_url=API.OAUTH2_API_URL)
332+
url = '{base_auth_url}/token'.format(base_auth_url=self._api_config.OAUTH2_API_URL)
324333
headers = {'content-type': 'application/x-www-form-urlencoded'}
325334
network_response = self._network_layer.request(
326335
'POST',
@@ -373,7 +382,7 @@ def revoke(self):
373382
token_to_revoke = access_token or refresh_token
374383
if token_to_revoke is None:
375384
return
376-
url = '{base_auth_url}/revoke'.format(base_auth_url=API.OAUTH2_API_URL)
385+
url = '{base_auth_url}/revoke'.format(base_auth_url=self._api_config.OAUTH2_API_URL)
377386
network_response = self._network_layer.request(
378387
'POST',
379388
url,

boxsdk/client/client.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import json
55

66
from ..auth.oauth2 import TokenResponse
7-
from ..config import API
87
from ..session.box_session import BoxSession
98
from ..network.default_network import DefaultNetwork
109
from ..object.cloneable import Cloneable
@@ -166,7 +165,7 @@ def users(self, limit=None, offset=0, filter_term=None):
166165
:rtype:
167166
`list` of :class:`User`
168167
"""
169-
url = '{0}/users'.format(API.BASE_API_URL)
168+
url = self.get_url('users')
170169
params = dict(offset=offset)
171170
if limit is not None:
172171
params['limit'] = limit
@@ -278,7 +277,7 @@ def groups(self):
278277
:rtype:
279278
`list` of :class:`Group`
280279
"""
281-
url = '{0}/groups'.format(API.BASE_API_URL)
280+
url = self.get_url('groups')
282281
box_response = self._session.get(url)
283282
response = box_response.json()
284283
group_class = self.translator.translate('group')
@@ -304,7 +303,7 @@ def create_group(self, name):
304303
:raises:
305304
:class:`BoxAPIException` if current user doesn't have permissions to create a group.
306305
"""
307-
url = '{0}/groups'.format(API.BASE_API_URL)
306+
url = self.get_url('groups')
308307
body_attributes = {
309308
'name': name,
310309
}
@@ -375,7 +374,7 @@ def get_shared_item(self, shared_link, password=None):
375374
"""
376375
response = self.make_request(
377376
'GET',
378-
'{0}/shared_items'.format(API.BASE_API_URL),
377+
self.get_url('shared_items'),
379378
headers=get_shared_link_header(shared_link, password),
380379
).json()
381380
return self.translator.translate(response['type'])(
@@ -425,7 +424,7 @@ def create_user(self, name, login=None, **user_attributes):
425424
https://box-content.readme.io/#create-an-enterprise-user for enterprise users
426425
or https://box-content.readme.io/docs/app-users for app users.
427426
"""
428-
url = '{0}/users'.format(API.BASE_API_URL)
427+
url = self.get_url('users')
429428
user_attributes['name'] = name
430429
if login is not None:
431430
user_attributes['login'] = login
@@ -461,7 +460,7 @@ def downscope_token(self, scopes, item=None, additional_data=None):
461460
:rtype:
462461
:class:`TokenResponse`
463462
"""
464-
url = '{base_auth_url}/token'.format(base_auth_url=API.OAUTH2_API_URL)
463+
url = '{base_auth_url}/token'.format(base_auth_url=self._session.api_config.OAUTH2_API_URL)
465464
data = {
466465
'subject_token': self.auth.access_token,
467466
'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token',

boxsdk/object/file.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
from __future__ import unicode_literals
44

5-
from boxsdk.config import API
65
from .item import Item
76
from ..util.api_call_decorator import api_call
87

@@ -122,7 +121,10 @@ def update_contents_with_stream(
122121
if preflight_check:
123122
self.preflight_check(size=preflight_expected_size)
124123

125-
url = self.get_url('content').replace(API.BASE_API_URL, API.UPLOAD_URL)
124+
url = self.get_url('content').replace(
125+
self._session.api_config.BASE_API_URL,
126+
self._session.api_config.UPLOAD_URL,
127+
)
126128
if upload_using_accelerator:
127129
accelerator_upload_url = self._get_accelerator_upload_url_for_update()
128130
if accelerator_upload_url:

boxsdk/object/folder.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import os
66
from six import text_type
77

8-
from boxsdk.config import API
98
from boxsdk.object.group import Group
109
from boxsdk.object.item import Item
1110
from boxsdk.object.user import User
@@ -266,7 +265,7 @@ def upload_stream(
266265
if preflight_check:
267266
self.preflight_check(size=preflight_expected_size, name=file_name)
268267

269-
url = '{0}/files/content'.format(API.UPLOAD_URL)
268+
url = '{0}/files/content'.format(self._session.api_config.UPLOAD_URL)
270269
if upload_using_accelerator:
271270
accelerator_upload_url = self._get_accelerator_upload_url_fow_new_uploads()
272271
if accelerator_upload_url:
@@ -418,7 +417,7 @@ def add_collaborator(self, collaborator, role, notify=False, can_view_path=False
418417
:class:`Collaboration`
419418
"""
420419
collaborator_helper = _Collaborator(collaborator)
421-
url = API.BASE_API_URL + '/collaborations'
420+
url = self._session.get_url('collaborations')
422421
item = {'id': self._object_id, 'type': 'folder'}
423422
access_key, access_value = collaborator_helper.access
424423
accessible_by = {

boxsdk/object/group.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import json
66

77
from .base_object import BaseObject
8-
from ..config import API
98
from ..util.api_call_decorator import api_call
109

1110

@@ -73,7 +72,7 @@ def add_member(self, user, role):
7372
:rtype:
7473
:class:`GroupMembership`
7574
"""
76-
url = '{0}/group_memberships'.format(API.BASE_API_URL)
75+
url = self._session.get_url('group_memberships')
7776
body_attributes = {
7877
'user': {'id': user.object_id},
7978
'group': {'id': self.object_id},

boxsdk/object/item.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import json
55

66
from .base_object import BaseObject
7-
from ..config import API
87
from ..exception import BoxAPIException
98
from .metadata import Metadata
109
from ..util.api_call_decorator import api_call
@@ -27,7 +26,7 @@ def _get_accelerator_upload_url(self, file_id=None):
2726
`unicode` or None
2827
"""
2928
endpoint = '{0}/content'.format(file_id) if file_id else 'content'
30-
url = '{0}/files/{1}'.format(API.BASE_API_URL, endpoint)
29+
url = '{0}/files/{1}'.format(self._session.api_config.BASE_API_URL, endpoint)
3130
try:
3231
response_json = self._session.options(
3332
url=url,
@@ -63,7 +62,7 @@ def _preflight_check(self, size, name=None, file_id=None, parent_id=None):
6362
:class:`BoxAPIException` when preflight check fails.
6463
"""
6564
endpoint = '{0}/content'.format(file_id) if file_id else 'content'
66-
url = '{0}/files/{1}'.format(API.BASE_API_URL, endpoint)
65+
url = '{0}/files/{1}'.format(self._session.api_config.BASE_API_URL, endpoint)
6766
data = {'size': size}
6867
if name:
6968
data['name'] = name

boxsdk/session/box_session.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,16 @@ class BoxSession(object):
1818
Box API session. Provides auth, automatic retry of failed requests, and session renewal.
1919
"""
2020

21-
def __init__(self, oauth, network_layer, default_headers=None, translator=None, default_network_request_kwargs=None):
21+
def __init__(
22+
self,
23+
oauth,
24+
network_layer,
25+
default_headers=None,
26+
translator=None,
27+
default_network_request_kwargs=None,
28+
api_config=None,
29+
client_config=None,
30+
):
2231
"""
2332
:param oauth:
2433
OAuth2 object used by the session to authorize requests.
@@ -43,13 +52,23 @@ def __init__(self, oauth, network_layer, default_headers=None, translator=None,
4352
when this session makes an API request.
4453
:type default_network_request_kwargs:
4554
`dict` or None
55+
:param api_config:
56+
Object containing URLs for the Box API.
57+
:type api_config:
58+
:class:`API`
59+
:param client_config:
60+
Object containing client information, including user agent string.
61+
:type client_config:
62+
:class:`Client`
4663
"""
4764
if translator is None:
4865
translator = Translator(extend_default_translator=True, new_child=True)
66+
self._api_config = api_config or API()
67+
self._client_config = client_config or Client()
4968
super(BoxSession, self).__init__()
5069
self._oauth = oauth
5170
self._network_layer = network_layer
52-
self._default_headers = {'User-Agent': Client.USER_AGENT_STRING}
71+
self._default_headers = {'User-Agent': self._client_config.USER_AGENT_STRING}
5372
self._translator = translator
5473
self._default_network_request_kwargs = {}
5574
if default_headers:
@@ -75,6 +94,22 @@ def translator(self):
7594
"""
7695
return self._translator
7796

97+
@property
98+
def api_config(self):
99+
"""
100+
101+
:rtype: :class:`API`
102+
"""
103+
return self._api_config
104+
105+
@property
106+
def client_config(self):
107+
"""
108+
109+
:rtype: :class:`Client`
110+
"""
111+
return self._client_config
112+
78113
def get_url(self, endpoint, *args):
79114
"""
80115
Return the URL for the given Box API endpoint.
@@ -91,7 +126,7 @@ def get_url(self, endpoint, *args):
91126
`unicode`
92127
"""
93128
# pylint:disable=no-self-use
94-
url = ['{0}/{1}'.format(API.BASE_API_URL, endpoint)]
129+
url = ['{0}/{1}'.format(self._api_config.BASE_API_URL, endpoint)]
95130
url.extend(['/{0}'.format(x) for x in args])
96131
return ''.join(url)
97132

@@ -112,6 +147,8 @@ def as_user(self, user):
112147
default_headers=headers,
113148
translator=self._translator,
114149
default_network_request_kwargs=self._default_network_request_kwargs.copy(),
150+
api_config=self._api_config,
151+
client_config=self._client_config,
115152
)
116153

117154
def with_shared_link(self, shared_link, shared_link_password=None):
@@ -135,6 +172,8 @@ def with_shared_link(self, shared_link, shared_link_password=None):
135172
default_headers=headers,
136173
translator=self._translator,
137174
default_network_request_kwargs=self._default_network_request_kwargs.copy(),
175+
api_config=self._api_config,
176+
client_config=self._client_config,
138177
)
139178

140179
def with_default_network_request_kwargs(self, extra_network_parameters):
@@ -144,6 +183,8 @@ def with_default_network_request_kwargs(self, extra_network_parameters):
144183
default_headers=self._default_headers.copy(),
145184
translator=self._translator,
146185
default_network_request_kwargs=extra_network_parameters,
186+
api_config=self._api_config,
187+
client_config=self._client_config,
147188
)
148189

149190
def _renew_session(self, access_token_used):

test/unit/conftest.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
from mock import Mock, MagicMock
99
import pytest
10+
1011
from boxsdk.auth.oauth2 import DefaultNetwork
12+
from boxsdk.config import API, Client
1113
from boxsdk.network import default_network
1214
from boxsdk.network.default_network import DefaultNetworkResponse
1315
from boxsdk.session.box_response import BoxResponse
@@ -46,6 +48,10 @@ def translator(default_translator): # pylint:disable=unused-argument
4648
@pytest.fixture(scope='function')
4749
def mock_box_session(translator):
4850
mock_session = MagicMock(BoxSession)
51+
# pylint:disable=protected-access
52+
mock_session._api_config = mock_session.api_config = API()
53+
mock_session._client_config = mock_session.client_config = Client()
54+
# pylint:enable=protected-access
4955
mock_session.get_url.side_effect = lambda *args, **kwargs: BoxSession.get_url(mock_session, *args, **kwargs)
5056
mock_session.translator = translator
5157
return mock_session
@@ -54,6 +60,10 @@ def mock_box_session(translator):
5460
@pytest.fixture()
5561
def mock_box_session_2():
5662
mock_session = MagicMock(BoxSession)
63+
# pylint:disable=protected-access
64+
mock_session._api_config = API()
65+
mock_session._client_config = Client()
66+
# pylint:enable=protected-access
5767
mock_session.get_url.side_effect = lambda *args, **kwargs: BoxSession.get_url(mock_session, *args, **kwargs)
5868
return mock_session
5969

test/unit/session/test_box_session.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import pytest
1111

1212
from boxsdk.auth.oauth2 import OAuth2
13+
from boxsdk.config import API
1314
from boxsdk.exception import BoxAPIException
1415
from boxsdk.network.default_network import DefaultNetwork, DefaultNetworkResponse
1516
from boxsdk.session.box_response import BoxResponse
@@ -252,3 +253,18 @@ class Foo(object):
252253
# Test that adding new registrations does not affect global state.
253254
assert default_translator == original_default_translator
254255
assert (set(box_session.translator) - set(default_translator)) == set([item_type])
256+
257+
258+
def test_session_uses_global_config(box_session, mock_network_layer, generic_successful_response, monkeypatch):
259+
mock_network_layer.request.side_effect = generic_successful_response
260+
example_dot_com = 'https://example.com/'
261+
monkeypatch.setattr(API, 'BASE_API_URL', example_dot_com)
262+
assert example_dot_com in box_session.get_url('foo', 'bar')
263+
264+
265+
def test_session_uses_local_config(box_session, mock_network_layer, generic_successful_response, monkeypatch):
266+
mock_network_layer.request.side_effect = generic_successful_response
267+
example_dot_com = 'https://example.com/'
268+
box_session.api_config.BASE_API_URL = example_dot_com
269+
monkeypatch.setattr(API, 'BASE_API_URL', 'https://api.box.com')
270+
assert example_dot_com in box_session.get_url('foo', 'bar')

0 commit comments

Comments
 (0)