forked from canada-ca/tracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcore.py
More file actions
129 lines (97 loc) · 4.33 KB
/
core.py
File metadata and controls
129 lines (97 loc) · 4.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
"""This module provides utility functions related to gql, mostly for internal use"""
import os
import re
from gql import Client
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.exceptions import (
TransportQueryError,
TransportServerError,
TransportProtocolError,
)
from queries import SIGNIN_MUTATION
JWT_RE = r"^[A-Za-z0-9-_=]+\.[A-Za-z0-9-_=]+\.?[A-Za-z0-9-_.+/=]*$"
"""Regex to validate a JWT"""
def create_transport(url, auth_token=None):
"""Create and return a gql transport object
Users should rarely, if ever, need to call this
:param str url: the Tracker GraphQL endpoint url
:param str auth_token: JWT auth token, omit when initially obtaining the token (default is none)
:return: A gql transport for given url
:rtype: AIOHTTPTransport
:raises ValueError: if auth_token is not a valid JWT
:raises TypeError: if auth_token is not a string
"""
if auth_token is None:
transport = AIOHTTPTransport(url=url)
else:
# Resulting stack trace is very unhelpful when passing an invalid token
# We validate the given auth_token and raise an exception if it's invalid
# to make debugging easier
if not isinstance(auth_token, str):
raise TypeError("auth_token must be a string")
if not re.match(JWT_RE, auth_token):
raise ValueError("auth_token is not a valid JWT")
transport = AIOHTTPTransport(
url=url,
headers={"authorization": auth_token},
)
return transport
def create_client(url="https://tracker.alpha.canada.ca/graphql", auth_token=None):
"""Create and return a gql client object
:param str url: the Tracker GraphQL endpoint url (default is "https://tracker.alpha.canada.ca/graphql")
:param str auth_token: JWT auth token, omit when initially obtaining the token (default is None)
:return: A gql client with AIOHTTPTransport
:rtype: Client
"""
client = Client(
transport=create_transport(url=url, auth_token=auth_token),
fetch_schema_from_transport=True,
)
return client
def get_auth_token(url="https://tracker.alpha.canada.ca/graphql"):
"""Get a token to use for authentication.
Takes in environment variables "TRACKER_UNAME" and "TRACKER_PASS" to get credentials
:param str url: the Tracker GraphQL endpoint url (default is "https://tracker.alpha.canada.ca/graphql")
:return: JWT auth token to allow access to Tracker
:rtype: str
"""
client = create_client(url)
username = os.environ.get("TRACKER_UNAME")
password = os.environ.get("TRACKER_PASS")
if username is None or password is None:
raise ValueError("Tracker credentials missing from environment.")
params = {"creds": {"userName": username, "password": password}}
result = client.execute(SIGNIN_MUTATION, variable_values=params)
auth_token = result["signIn"]["result"]["authResult"]["authToken"]
return auth_token
# TODO: Make error messages better
def execute_query(client, query, params=None):
"""Executes a query on given client, with given parameters.
Intended for internal use, but if for some reason you need an unformatted
response from the API you could call this.
:param Client client: a gql client to execute the query on
:param DocumentNode query: a gql query string that has been parsed with gql()
:param dict params: variables to pass along with query
:return: Results of executing query on API
:rtype: dict
:raises TransportProtocolError: if server response is not GraphQL
:raises TransportServerError: if there is a server error
:raises Exception: if any unhandled exception is raised within function
"""
try:
result = client.execute(query, variable_values=params)
except TransportQueryError as error:
# Not sure this is the best way to deal with this exception
result = {"error": {"message": error.errors[0]["message"]}}
except TransportProtocolError as error:
print("Unexpected response from server:", error)
raise
except TransportServerError as error:
print("Server error:", error)
raise
except Exception as error:
# Need to be more descriptive
# Potentially figure out other errors that could be caught here?
print("Fatal error:", error)
raise
return result