Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

codegen output 13ed41f186c7473db272e90659610b17 #41

Merged
merged 1 commit into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading