Skip to content

Commit

Permalink
generated with codegen at box/box-codegen@23a331c6 and spec at box/bo…
Browse files Browse the repository at this point in the history
  • Loading branch information
box-sdk-build committed Nov 9, 2023
1 parent 2bdbd8a commit 1283a8c
Show file tree
Hide file tree
Showing 87 changed files with 1,993 additions and 1,713 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
USER_ID: ${{ secrets.USER_ID }}
ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }}
BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }}
run: |
tox
Expand Down Expand Up @@ -67,3 +68,4 @@ jobs:
CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
USER_ID: ${{ secrets.USER_ID }}
ENTERPRISE_ID: ${{ secrets.ENTERPRISE_ID }}
BOX_FILE_REQUEST_ID: ${{ secrets.BOX_FILE_REQUEST_ID }}
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ if __name__ == '__main__':

### Create Custom Application

To run integration tests locally you will need a `Custom App` created at https://cloud.app.box.com/developers/console
To run integration tests locally you will need a `Custom App` created in the [Box Developer
Console](https://app.box.com/developers/console)
with `Server Authentication (with JWT)` selected as authentication method.
Once created you can edit properties of the application:

Expand All @@ -96,6 +97,7 @@ Now select `Authorization` and submit application to be reviewed by account admi
download your app configuration settings as JSON.
2. Encode configuration file to Base64, e.g. using command: `base64 -i path_to_json_file`
3. Set environment variable: `JWT_CONFIG_BASE_64` with base64 encoded jwt configuration file
4. Set environment variable: `BOX_FILE_REQUEST_ID` with ID of file request already created in the user account.

### Running tests

Expand Down
6 changes: 3 additions & 3 deletions box_sdk_gen/ccg_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,13 @@ def refresh_token(
'https://api.box.com/oauth2/token',
FetchOptions(
method='POST',
body=urlencode(request_body.to_dict()),
headers={'content-type': 'application/x-www-form-urlencoded'},
data=request_body.to_dict(),
content_type='application/x-www-form-urlencoded',
network_session=network_session,
),
)

new_token = AccessToken.from_dict(json.loads(response.text))
new_token = AccessToken.from_dict(response.data)
self.token_storage.store(new_token)
return new_token

Expand Down
43 changes: 39 additions & 4 deletions box_sdk_gen/client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Dict

from box_sdk_gen.managers.authorization import AuthorizationManager

Expand Down Expand Up @@ -166,9 +166,7 @@


class BoxClient:
def __init__(
self, auth: Authentication, network_session: Optional[NetworkSession] = None
):
def __init__(self, auth: Authentication, network_session: NetworkSession = None):
if network_session is None:
network_session = NetworkSession()
self.auth = auth
Expand Down Expand Up @@ -381,3 +379,40 @@ def __init__(
self.integration_mappings = IntegrationMappingsManager(
auth=self.auth, network_session=self.network_session
)

def with_as_user_header(self, user_id: str) -> 'BoxClient':
"""
Create a new client to impersonate user with the provided ID. All calls made with the new client will be made in context of the impersonated user, leaving the original client unmodified.
:param user_id: ID of an user to impersonate
:type user_id: str
"""
return BoxClient(
auth=self.auth,
network_session=self.network_session.with_additional_headers({
'As-User': user_id
}),
)

def with_suppressed_notifications(self) -> 'BoxClient':
"""
Create a new client with suppressed notifications. Calls made with the new client will not trigger email or webhook notifications
"""
return BoxClient(
auth=self.auth,
network_session=self.network_session.with_additional_headers({
'Box-Notifications': 'off'
}),
)

def with_extra_headers(self, extra_headers: Dict[str, str] = None) -> 'BoxClient':
"""
Create a new client with a custom set of headers that will be included in every API call
:param extra_headers: Custom set of headers that will be included in every API call
:type extra_headers: Dict[str, str], optional
"""
if extra_headers is None:
extra_headers = {}
return BoxClient(
auth=self.auth,
network_session=self.network_session.with_additional_headers(extra_headers),
)
50 changes: 37 additions & 13 deletions box_sdk_gen/fetch.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .network import NetworkSession
from .auth import Authentication
from .utils import ByteStream, ResponseByteStream
from .json import SerializedData, sd_to_json, sd_to_url_params, json_to_serialized_data

DEFAULT_MAX_ATTEMPTS = 5
_RETRY_RANDOMIZATION_FACTOR = 0.5
Expand All @@ -30,7 +31,7 @@
@dataclass
class MultipartItem:
part_name: str
body: str = None
data: SerializedData = None
file_stream: ByteStream = None
file_name: str = ''
content_type: str = None
Expand All @@ -41,10 +42,10 @@ class FetchOptions:
method: str = "GET"
params: Dict[str, str] = None
headers: Dict[str, str] = None
body: str = None
data: SerializedData = None
file_stream: ByteStream = None
multipart_data: List[MultipartItem] = None
content_type: str = ""
content_type: str = "application/json"
response_format: Optional[str] = None
auth: Authentication = None
network_session: NetworkSession = None
Expand All @@ -53,7 +54,7 @@ class FetchOptions:
@dataclass
class FetchResponse:
status: int
text: Optional[str] = None
data: Optional[SerializedData] = None
content: Optional[ByteStream] = None


Expand Down Expand Up @@ -101,7 +102,7 @@ def fetch(url: str, options: FetchOptions) -> FetchResponse:
method=options.method,
url=url,
headers=headers,
body=options.file_stream or options.body,
data=options.file_stream or options.data,
content_type=options.content_type,
params=params,
multipart_data=options.multipart_data,
Expand All @@ -120,7 +121,11 @@ def fetch(url: str, options: FetchOptions) -> FetchResponse:
else:
return FetchResponse(
status=response.status_code,
text=response.text,
data=(
json_to_serialized_data(response.text)
if response.text
else None
),
content=io.BytesIO(response.content),
)

Expand Down Expand Up @@ -148,7 +153,7 @@ def fetch(url: str, options: FetchOptions) -> FetchResponse:
method=options.method,
url=url,
headers=headers,
body=options.body,
data=options.file_stream or options.data,
content_type=options.content_type,
params=params,
multipart_data=options.multipart_data,
Expand All @@ -162,7 +167,11 @@ def fetch(url: str, options: FetchOptions) -> FetchResponse:


def __compose_headers_for_request(options: FetchOptions) -> Dict[str, str]:
headers = options.headers or {}
headers = {}
if options.network_session:
headers.update(options.network_session.additional_headers)
if options.headers:
headers.update(options.headers)
if options.auth:
headers['Authorization'] = (
'Bearer'
Expand All @@ -179,7 +188,7 @@ def __make_request(
method,
url,
headers,
body,
data,
content_type,
params,
multipart_data,
Expand All @@ -189,8 +198,8 @@ def __make_request(
if content_type == 'multipart/form-data':
fields = OrderedDict()
for part in multipart_data:
if part.body:
fields[part.part_name] = part.body
if part.data:
fields[part.part_name] = sd_to_json(part.data)
else:
file_stream = part.file_stream
file_stream_position = file_stream.tell()
Expand All @@ -202,18 +211,33 @@ def __make_request(
)

multipart_stream = MultipartEncoder(fields)
body = multipart_stream
data = multipart_stream
headers['Content-Type'] = multipart_stream.content_type
else:
headers['Content-Type'] = content_type

def get_data():
if (
content_type == 'application/json'
or content_type == 'application/json-patch+json'
):
return sd_to_json(data) if data else None
if content_type == 'application/x-www-form-urlencoded':
return sd_to_url_params(data)
if (
content_type == 'multipart/form-data'
or content_type == 'application/octet-stream'
):
return data
raise

raised_exception = None
try:
network_response = session.request(
method=method,
url=url,
headers=headers,
data=body,
data=get_data(),
params=params,
stream=True,
)
Expand Down
18 changes: 18 additions & 0 deletions box_sdk_gen/json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json
from urllib.parse import urlencode


class SerializedData:
pass


def json_to_serialized_data(data: str) -> SerializedData:
return json.loads(data)


def sd_to_json(data: SerializedData) -> str:
return json.dumps(data)


def sd_to_url_params(data: SerializedData) -> str:
return urlencode(data)
10 changes: 5 additions & 5 deletions box_sdk_gen/jwt_auth.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from datetime import datetime, timedelta
import json
import random
import string

Expand All @@ -23,6 +22,7 @@
from .fetch import fetch, FetchResponse, FetchOptions
from .network import NetworkSession
from .schemas import AccessToken
from .json import json_to_serialized_data


class JWTConfig:
Expand Down Expand Up @@ -106,7 +106,7 @@ def from_config_json_string(
:return:
Auth instance configured as specified by the config dictionary.
"""
config_dict: dict = json.loads(config_json_string)
config_dict: dict = json_to_serialized_data(config_json_string)
if 'boxAppSettings' not in config_dict:
raise ValueError('boxAppSettings not present in configuration')
return cls(
Expand Down Expand Up @@ -232,13 +232,13 @@ def refresh_token(
'https://api.box.com/oauth2/token',
FetchOptions(
method='POST',
body=urlencode(request_body.to_dict()),
headers={'content-type': 'application/x-www-form-urlencoded'},
data=request_body.to_dict(),
content_type='application/x-www-form-urlencoded',
network_session=network_session,
),
)

new_token = AccessToken.from_dict(json.loads(response.text))
new_token = AccessToken.from_dict(response.data)
self.token_storage.store(new_token)
return new_token

Expand Down
18 changes: 9 additions & 9 deletions box_sdk_gen/managers/authorization.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

from box_sdk_gen.utils import ByteStream

from box_sdk_gen.json import sd_to_json

from box_sdk_gen.fetch import fetch

from box_sdk_gen.fetch import FetchOptions
Expand Down Expand Up @@ -99,15 +101,13 @@ def get_authorize(
"""
if extra_headers is None:
extra_headers = {}
query_params_map: Dict[str, str] = prepare_params(
{
'response_type': to_string(response_type),
'client_id': to_string(client_id),
'redirect_uri': to_string(redirect_uri),
'state': to_string(state),
'scope': to_string(scope),
}
)
query_params_map: Dict[str, str] = prepare_params({
'response_type': to_string(response_type),
'client_id': to_string(client_id),
'redirect_uri': to_string(redirect_uri),
'state': to_string(state),
'scope': to_string(scope),
})
headers_map: Dict[str, str] = prepare_params({**extra_headers})
response: FetchResponse = fetch(
''.join(['https://account.box.com/api/oauth2/authorize']),
Expand Down
6 changes: 5 additions & 1 deletion box_sdk_gen/managers/avatars.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@

from box_sdk_gen.fetch import FetchResponse

from box_sdk_gen.json import sd_to_json

from box_sdk_gen.fetch import MultipartItem

from box_sdk_gen.json import SerializedData


class AvatarsManager:
def __init__(
Expand Down Expand Up @@ -111,7 +115,7 @@ def create_user_avatar(
network_session=self.network_session,
),
)
return deserialize(response.text, UserAvatar)
return deserialize(response.data, UserAvatar)

def delete_user_avatar(
self, user_id: str, extra_headers: Optional[Dict[str, Optional[str]]] = None
Expand Down
Loading

0 comments on commit 1283a8c

Please sign in to comment.