Skip to content
Closed
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: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
python -m pip install --upgrade pip
pip install pipenv
pipenv install --dev --deploy --system
pip install requests
pip install urllib3

- name: Run Tests
run: |
Expand Down
2 changes: 1 addition & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ pylint = "*"
pytest = "*"

[packages]
requests = "*"
urllib3 = "*"

[requires]
599 changes: 239 additions & 360 deletions Pipfile.lock

Large diffs are not rendered by default.

66 changes: 40 additions & 26 deletions monday/graphqlclient/client.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import json
import requests

import urllib3

from monday.exceptions import MondayQueryError

TOKEN_HEADER = 'Authorization'
TOKEN_HEADER = "Authorization"

DEFAULT_TIMEOUT = 60

Expand All @@ -16,6 +17,7 @@ def __init__(self, endpoint, timeout=DEFAULT_TIMEOUT):
self.timeout = timeout
self.token = None
self.headers = {}
self._http = urllib3.PoolManager()

def execute(self, query, variables=None):
return self._send(query, variables)
Expand All @@ -27,34 +29,46 @@ def inject_headers(self, headers):
self.headers = headers

def _send(self, query, variables):
payload = {'query': query}
headers = self.headers.copy()
files = None

if self.token is not None:
headers[TOKEN_HEADER] = self.token

if variables is None:
headers.setdefault('Content-Type', 'application/json')

payload = json.dumps({'query': query}).encode('utf-8')

elif variables.get('file', None) is not None:
headers.setdefault('content', 'multipart/form-data')

files = [
('variables[file]', (variables['file'], open(variables['file'], 'rb')))
]

try:
response = requests.request("POST", self.endpoint, headers=headers, data=payload, files=files, timeout=self.timeout)
response.raise_for_status()
response_data = response.json()
self._throw_on_error(response_data)
return response_data
except (requests.HTTPError, json.JSONDecodeError, MondayQueryError) as e:
raise e
if variables is not None and variables.get("file") is not None:
file_path = variables["file"]
with open(file_path, "rb") as f:
file_data = f.read()

response = self._http.request(
"POST",
self.endpoint,
headers=headers,
fields={"query": query, "variables[file]": (file_path, file_data)},
timeout=self.timeout,
)
else:
headers.setdefault("Content-Type", "application/json")
body = json.dumps({"query": query}).encode("utf-8")

response = self._http.request(
"POST",
self.endpoint,
headers=headers,
body=body,
timeout=self.timeout,
)

if response.status >= 400:
raise urllib3.exceptions.HTTPError(
f"HTTP {response.status}: {response.data.decode('utf-8')}"
)

response_data = json.loads(response.data.decode("utf-8"))
self._throw_on_error(response_data)
return response_data

def _throw_on_error(self, response_data):
if 'errors' in response_data:
raise MondayQueryError(response_data['errors'][0]['message'], response_data['errors'])
if "errors" in response_data:
raise MondayQueryError(
response_data["errors"][0]["message"], response_data["errors"]
)
43 changes: 42 additions & 1 deletion monday/tests/test_graphql_client.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import unittest
from unittest.mock import patch, MagicMock
import json
import urllib3

from monday.graphqlclient.client import GraphQLClient
from monday.exceptions import MondayQueryError


class GraphQlClientTestCase(unittest.TestCase):
def setUp(self):
self.token = "foo"
self.url = "https://api.monday.com/v2'"
self.url = "https://api.monday.com/v2"
self.client = GraphQLClient(self.url)

def test_client_endpoint(self):
Expand All @@ -15,3 +20,39 @@ def test_inject_token(self):
client = self.client
client.inject_token(token=self.token)
self.assertEqual(client.token, self.token)

def test_client_has_pool_manager(self):
self.assertIsInstance(self.client._http, urllib3.PoolManager)

@patch.object(urllib3.PoolManager, 'request')
def test_execute_sends_post(self, mock_request):
mock_response = MagicMock()
mock_response.status = 200
mock_response.data = json.dumps({'data': {'boards': []}}).encode('utf-8')
mock_request.return_value = mock_response

self.client.inject_token(self.token)
result = self.client.execute('{ boards { id } }')

mock_request.assert_called_once()
self.assertEqual(result, {'data': {'boards': []}})

@patch.object(urllib3.PoolManager, 'request')
def test_execute_raises_on_http_error(self, mock_request):
mock_response = MagicMock()
mock_response.status = 500
mock_response.data = b'Internal Server Error'
mock_request.return_value = mock_response

with self.assertRaises(urllib3.exceptions.HTTPError):
self.client.execute('{ boards { id } }')

@patch.object(urllib3.PoolManager, 'request')
def test_execute_raises_on_query_error(self, mock_request):
mock_response = MagicMock()
mock_response.status = 200
mock_response.data = json.dumps({'errors': [{'message': 'bad query'}]}).encode('utf-8')
mock_request.return_value = mock_response

with self.assertRaises(MondayQueryError):
self.client.execute('{ bad }')
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
zip_safe=False,
license="BSD",
install_requires=[
"requests>=2.30.0",
"urllib3>=2.6.0",
],
python_requires=">=3.11",
classifiers=[
Expand Down
Loading