1
+ import platform
1
2
import urllib .parse
2
- from typing import Any , Optional , Tuple , Union
3
+ from typing import Any , Dict , Iterable , List , Optional , Tuple , Union
3
4
4
5
import requests
5
6
from marshmallow import Schema
@@ -18,26 +19,22 @@ class GGClient:
18
19
DETAIL_SCHEMA = DetailSchema ()
19
20
DOCUMENT_SCHEMA = DocumentSchema ()
20
21
SCAN_RESULT_SCHEMA = ScanResultSchema ()
22
+ _version = "undefined"
21
23
22
24
def __init__ (
23
25
self ,
24
26
token : str ,
25
- base_uri : str = None ,
26
- session : requests .Session = None ,
27
- user_agent : str = "" ,
28
- timeout : float = _DEFAULT_TIMEOUT ,
29
- ):
27
+ base_uri : Optional [ str ] = None ,
28
+ session : Optional [ requests .Session ] = None ,
29
+ user_agent : Optional [ str ] = None ,
30
+ timeout : Optional [ float ] = _DEFAULT_TIMEOUT ,
31
+ ) -> "GGClient" :
30
32
"""
31
33
:param token: APIKey to be added to requests
32
- :type token: str
33
34
:param base_uri: Base URI for the API, defaults to "https://api.gitguardian.com"
34
- :type base_uri: str, optional
35
35
:param session: custom requests session, defaults to requests.Session()
36
- :type session: requests.Session, optional
37
36
:param user_agent: user agent to identify requests, defaults to ""
38
- :type user_agent: str, optional
39
37
:param timeout: request timeout, defaults to 20s
40
- :type timeout: float, optional
41
38
42
39
:raises ValueError: if the protocol is invalid
43
40
"""
@@ -57,12 +54,15 @@ def __init__(
57
54
session if session is isinstance (session , Session ) else requests .Session ()
58
55
)
59
56
self .timeout = timeout
57
+ self .user_agent = "pygitguardian/{0} ({1};py{2})" .format (
58
+ self ._version , platform .system (), platform .python_version ()
59
+ )
60
+
61
+ if user_agent :
62
+ self .user_agent = " " .join ([self .user_agent , user_agent ])
60
63
61
64
self .session .headers .update (
62
- {
63
- "User-Agent" : " " .join (["pygitguardian" , user_agent ]),
64
- "Authorization" : "Token {0}" .format (token ),
65
- }
65
+ {"User-Agent" : self .user_agent , "Authorization" : "Token {0}" .format (token )}
66
66
)
67
67
68
68
def request (
@@ -71,6 +71,7 @@ def request(
71
71
endpoint : str ,
72
72
schema : Schema = None ,
73
73
version : str = _API_VERSION ,
74
+ many : bool = False ,
74
75
** kwargs
75
76
) -> Tuple [Any , Response ]:
76
77
if version :
@@ -86,11 +87,15 @@ def request(
86
87
raise TypeError ("Response is not JSON" )
87
88
88
89
if response .status_code == codes .ok and schema :
89
- obj = schema .load (response .json ())
90
+ obj = schema .load (response .json (), many = many )
91
+ if many :
92
+ for element in obj :
93
+ element .status_code = response .status_code
94
+ else :
95
+ obj .status_code = response .status_code
90
96
else :
91
97
obj = self .DETAIL_SCHEMA .load (response .json ())
92
-
93
- obj .status_code = response .status_code
98
+ obj .status_code = response .status_code
94
99
95
100
return obj , response
96
101
@@ -100,6 +105,7 @@ def post(
100
105
data : str = None ,
101
106
schema : Schema = None ,
102
107
version : str = _API_VERSION ,
108
+ many : bool = False ,
103
109
** kwargs
104
110
) -> Tuple [Any , Response ]:
105
111
return self .request (
@@ -108,6 +114,7 @@ def post(
108
114
schema = schema ,
109
115
json = data ,
110
116
version = version ,
117
+ many = many ,
111
118
** kwargs ,
112
119
)
113
120
@@ -116,6 +123,7 @@ def get(
116
123
endpoint : str ,
117
124
schema : Schema = None ,
118
125
version : str = _API_VERSION ,
126
+ many : bool = False ,
119
127
** kwargs
120
128
) -> Tuple [Any , Response ]:
121
129
return self .request (
@@ -124,37 +132,58 @@ def get(
124
132
125
133
def content_scan (
126
134
self , document : str , filename : Optional [str ] = None
127
- ) -> Union [Detail , ScanResult ]:
128
- """content_scan handles the /scan endpoint of the API
129
-
130
- use filename=dummy to avoid evalutation of filename and file extension policies
135
+ ) -> Tuple [Union [Detail , ScanResult ], int ]:
136
+ """
137
+ content_scan handles the /scan endpoint of the API
131
138
132
139
:param filename: name of file, example: "intro.py"
133
- :type filename: str
134
140
:param document: content of file
135
- :type document: str
136
- :return: Detail or ScanResult response
137
- :rtype: Union[Detail, ScanResult]
141
+ :return: Detail or ScanResult response and status code
138
142
"""
139
143
140
144
doc_dict = {"document" : document }
141
145
if filename :
142
146
doc_dict ["filename" ] = filename
143
147
144
148
request_obj = self .DOCUMENT_SCHEMA .load (doc_dict )
145
- obj , _ = self .post (
149
+ obj , resp = self .post (
146
150
endpoint = "scan" , data = request_obj , schema = self .SCAN_RESULT_SCHEMA
147
151
)
148
- return obj
152
+ return obj , resp .status_code
153
+
154
+ def multi_content_scan (
155
+ self , documents : Iterable [Dict [str , str ]],
156
+ ) -> Tuple [Union [Detail , List [ScanResult ]], int ]:
157
+ """
158
+ multi_content_scan handles the /multiscan endpoint of the API
159
+
160
+ :param documents: List of dictionaries containing the keys document
161
+ and, optionaly, filename.
162
+ example: [{"document":"example content","filename":"intro.py"}]
163
+ :return: Detail or ScanResult response and status code
164
+ """
165
+
166
+ if all (isinstance (doc , dict ) for doc in documents ):
167
+ request_obj = self .DOCUMENT_SCHEMA .load (documents , many = True )
168
+ else :
169
+ raise TypeError ("documents must be a dict" )
170
+
171
+ obj , resp = self .post (
172
+ endpoint = "multiscan" ,
173
+ data = request_obj ,
174
+ schema = self .SCAN_RESULT_SCHEMA ,
175
+ many = True ,
176
+ )
177
+ return obj , resp .status_code
149
178
150
- def health_check (self ) -> Detail :
151
- """health_check handles the /health endpoint of the API
179
+ def health_check (self ) -> Tuple [Detail , int ]:
180
+ """
181
+ health_check handles the /health endpoint of the API
152
182
153
183
use Detail.status_code to check the response status code of the API
154
184
155
185
200 if server is online and token is valid
156
- :return: Detail response,
157
- :rtype: Detail
186
+ :return: Detail response and status code
158
187
"""
159
- obj , _ = self .get (endpoint = "health" )
160
- return obj
188
+ obj , resp = self .get (endpoint = "health" )
189
+ return obj , resp . status_code
0 commit comments