victorycode-sdk 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- victorycode_sdk/__init__.py +260 -0
- victorycode_sdk/api/__init__.py +13 -0
- victorycode_sdk/api/authentication_api.py +285 -0
- victorycode_sdk/api/classification_api.py +1203 -0
- victorycode_sdk/api/game_recap_api.py +1395 -0
- victorycode_sdk/api/games_api.py +1497 -0
- victorycode_sdk/api/mascot_api.py +1203 -0
- victorycode_sdk/api/plays_events_api.py +1057 -0
- victorycode_sdk/api/teams_api.py +1220 -0
- victorycode_sdk/api/uploads_api.py +872 -0
- victorycode_sdk/api/venue_api.py +1203 -0
- victorycode_sdk/api_client.py +833 -0
- victorycode_sdk/api_response.py +21 -0
- victorycode_sdk/configuration.py +698 -0
- victorycode_sdk/exceptions.py +218 -0
- victorycode_sdk/models/__init__.py +114 -0
- victorycode_sdk/models/add_video_to_game_with_s3_link_dto.py +90 -0
- victorycode_sdk/models/address.py +96 -0
- victorycode_sdk/models/address_dto.py +96 -0
- victorycode_sdk/models/bad_request_error_response_dto.py +90 -0
- victorycode_sdk/models/classification.py +90 -0
- victorycode_sdk/models/client_create_game_with_video_url_dto.py +130 -0
- victorycode_sdk/models/complete_multipart_upload_dto.py +97 -0
- victorycode_sdk/models/complete_multipart_upload_response.py +90 -0
- victorycode_sdk/models/complete_multipart_upload_response_dto.py +94 -0
- victorycode_sdk/models/completed_part_dto.py +90 -0
- victorycode_sdk/models/conflict_error_response_dto.py +90 -0
- victorycode_sdk/models/coordinates.py +90 -0
- victorycode_sdk/models/coordinates_dto.py +90 -0
- victorycode_sdk/models/create_classification_dto.py +90 -0
- victorycode_sdk/models/create_mascot_dto.py +90 -0
- victorycode_sdk/models/create_team_dto.py +108 -0
- victorycode_sdk/models/create_team_response_dto.py +106 -0
- victorycode_sdk/models/create_venue_dto.py +102 -0
- victorycode_sdk/models/defense_section_dto.py +101 -0
- victorycode_sdk/models/defense_stats_dto.py +106 -0
- victorycode_sdk/models/defense_totals_dto.py +100 -0
- victorycode_sdk/models/delete_response_dto.py +90 -0
- victorycode_sdk/models/down_efficiency_dto.py +90 -0
- victorycode_sdk/models/fetch_team_response_dto.py +117 -0
- victorycode_sdk/models/fumble_section_dto.py +101 -0
- victorycode_sdk/models/fumble_stats_dto.py +98 -0
- victorycode_sdk/models/fumble_totals_dto.py +92 -0
- victorycode_sdk/models/game_all_play_list_item_dto.py +106 -0
- victorycode_sdk/models/game_box_score_meta_dto.py +104 -0
- victorycode_sdk/models/game_box_score_response_dto.py +98 -0
- victorycode_sdk/models/game_box_score_teams_dto.py +97 -0
- victorycode_sdk/models/game_details_response.py +108 -0
- victorycode_sdk/models/game_recap_scoring_summary_pro_response.py +101 -0
- victorycode_sdk/models/game_recap_scoring_summary_response.py +101 -0
- victorycode_sdk/models/game_recap_team_stats_response.py +101 -0
- victorycode_sdk/models/game_score_response.py +101 -0
- victorycode_sdk/models/game_scoring_summary_data_dto.py +104 -0
- victorycode_sdk/models/get_parts_presign_url_response.py +88 -0
- victorycode_sdk/models/get_parts_presign_url_response_dto.py +94 -0
- victorycode_sdk/models/initiate_multipart_upload_dto.py +92 -0
- victorycode_sdk/models/initiate_multipart_upload_response.py +88 -0
- victorycode_sdk/models/initiate_multipart_upload_response_dto.py +94 -0
- victorycode_sdk/models/interception_section_dto.py +101 -0
- victorycode_sdk/models/interception_stats_dto.py +98 -0
- victorycode_sdk/models/interception_totals_dto.py +92 -0
- victorycode_sdk/models/kicking_section_dto.py +101 -0
- victorycode_sdk/models/kicking_stats_dto.py +106 -0
- victorycode_sdk/models/kicking_totals_dto.py +100 -0
- victorycode_sdk/models/line_of_scrimmage_dto.py +90 -0
- victorycode_sdk/models/list_classification_paginated_response_dto.py +101 -0
- victorycode_sdk/models/list_game_all_plays_response_dto.py +103 -0
- victorycode_sdk/models/list_games_paginated_response_dto.py +101 -0
- victorycode_sdk/models/list_mascot_paginated_response_dto.py +101 -0
- victorycode_sdk/models/list_play_clips_response_dto.py +105 -0
- victorycode_sdk/models/list_team_paginated_response_dto.py +101 -0
- victorycode_sdk/models/list_venue_paginated_response_dto.py +101 -0
- victorycode_sdk/models/list_video_paginated_response_dto.py +103 -0
- victorycode_sdk/models/mascot.py +90 -0
- victorycode_sdk/models/mascot_response_dto.py +96 -0
- victorycode_sdk/models/not_found_error_response_dto.py +90 -0
- victorycode_sdk/models/offense_dto.py +92 -0
- victorycode_sdk/models/passing_dto.py +98 -0
- victorycode_sdk/models/passing_section_dto.py +101 -0
- victorycode_sdk/models/passing_stats_dto.py +117 -0
- victorycode_sdk/models/passing_totals_dto.py +111 -0
- victorycode_sdk/models/period_score_dto.py +94 -0
- victorycode_sdk/models/play_attributes_list_dto.py +106 -0
- victorycode_sdk/models/play_clip_list_details_dto.py +104 -0
- victorycode_sdk/models/play_clip_list_item_dto.py +104 -0
- victorycode_sdk/models/pro_scoring_play_dto.py +202 -0
- victorycode_sdk/models/receiving_section_dto.py +101 -0
- victorycode_sdk/models/receiving_stats_dto.py +104 -0
- victorycode_sdk/models/receiving_totals_dto.py +98 -0
- victorycode_sdk/models/red_zone_dto.py +90 -0
- victorycode_sdk/models/rushing_dto.py +92 -0
- victorycode_sdk/models/rushing_section_dto.py +101 -0
- victorycode_sdk/models/rushing_stats_dto.py +102 -0
- victorycode_sdk/models/rushing_totals_dto.py +96 -0
- victorycode_sdk/models/scoring_play_player_involved_dto.py +106 -0
- victorycode_sdk/models/scoring_summary_pro_team_dto.py +90 -0
- victorycode_sdk/models/single_classification_response_dto.py +94 -0
- victorycode_sdk/models/single_mascot_response_dto.py +94 -0
- victorycode_sdk/models/single_team_response_dto.py +94 -0
- victorycode_sdk/models/single_venue_response_dto.py +99 -0
- victorycode_sdk/models/single_video_response_dto.py +94 -0
- victorycode_sdk/models/team_basic_info_dto.py +96 -0
- victorycode_sdk/models/team_box_score_dto.py +139 -0
- victorycode_sdk/models/team_score_dto.py +109 -0
- victorycode_sdk/models/team_stats_dto.py +137 -0
- victorycode_sdk/models/token_response.py +92 -0
- victorycode_sdk/models/turnovers_dto.py +92 -0
- victorycode_sdk/models/unauthorized_error_response_dto.py +90 -0
- victorycode_sdk/models/update_classification_dto.py +90 -0
- victorycode_sdk/models/update_mascot_dto.py +90 -0
- victorycode_sdk/models/update_team_dto.py +108 -0
- victorycode_sdk/models/update_venue_dto.py +102 -0
- victorycode_sdk/models/venue.py +102 -0
- victorycode_sdk/models/video_list_item_dto.py +96 -0
- victorycode_sdk/py.typed +0 -0
- victorycode_sdk/rest.py +334 -0
- victorycode_sdk-1.0.0.dist-info/METADATA +288 -0
- victorycode_sdk-1.0.0.dist-info/RECORD +121 -0
- victorycode_sdk-1.0.0.dist-info/WHEEL +5 -0
- victorycode_sdk-1.0.0.dist-info/licenses/LICENSE +21 -0
- victorycode_sdk-1.0.0.dist-info/top_level.txt +1 -0
victorycode_sdk/rest.py
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Victory Code Client API
|
|
5
|
+
|
|
6
|
+
Client APIs for Tactix partners: authenticate, upload game video, and retrieve games, plays, game recaps and team metadata.
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import ipaddress
|
|
16
|
+
import io
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import ssl
|
|
20
|
+
from urllib.parse import urlparse
|
|
21
|
+
|
|
22
|
+
import urllib3
|
|
23
|
+
|
|
24
|
+
from victorycode_sdk.exceptions import ApiException, ApiValueError
|
|
25
|
+
|
|
26
|
+
SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"}
|
|
27
|
+
RESTResponseType = urllib3.HTTPResponse
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def is_socks_proxy_url(url):
|
|
31
|
+
if url is None:
|
|
32
|
+
return False
|
|
33
|
+
split_section = url.split("://")
|
|
34
|
+
if len(split_section) < 2:
|
|
35
|
+
return False
|
|
36
|
+
else:
|
|
37
|
+
return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES
|
|
38
|
+
|
|
39
|
+
def contenttype_matches(contenttype, maintype, subtype):
|
|
40
|
+
"""Matches the given contenttype against the given type and subtype
|
|
41
|
+
|
|
42
|
+
:param contenttype: the content type to match
|
|
43
|
+
:param maintype: the expected maintype
|
|
44
|
+
:param subtype: the expected subtype
|
|
45
|
+
:return: `true` when the given content type matches the given type and subtype,
|
|
46
|
+
regardless of the presence of mime type parameters, otherwise returns `false`.
|
|
47
|
+
:rtype: bool
|
|
48
|
+
"""
|
|
49
|
+
pattern = '{type}/(?:[^+;]+\\+)?{subtype}(?:[ \t]*;.*)?'.format(
|
|
50
|
+
type = re.escape(maintype),
|
|
51
|
+
subtype = re.escape(subtype),
|
|
52
|
+
)
|
|
53
|
+
return re.fullmatch(pattern, contenttype, re.IGNORECASE) is not None
|
|
54
|
+
|
|
55
|
+
def should_bypass_proxies(url: str, no_proxy: str) -> bool:
|
|
56
|
+
"""Return whether ``url`` matches the comma-separated ``no_proxy`` rules."""
|
|
57
|
+
parsed_url = urlparse(url)
|
|
58
|
+
if not parsed_url.hostname:
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
host = parsed_url.hostname.lower()
|
|
62
|
+
host_and_port = parsed_url.netloc.lower()
|
|
63
|
+
try:
|
|
64
|
+
host_ip = ipaddress.ip_address(host)
|
|
65
|
+
except ValueError:
|
|
66
|
+
host_ip = None
|
|
67
|
+
|
|
68
|
+
for entry in (entry.strip().lower() for entry in no_proxy.split(',')):
|
|
69
|
+
if not entry:
|
|
70
|
+
continue
|
|
71
|
+
if entry == '*':
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
if host_ip is not None:
|
|
75
|
+
try:
|
|
76
|
+
if host_ip in ipaddress.ip_network(entry, strict=False):
|
|
77
|
+
return True
|
|
78
|
+
except ValueError:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
entry = entry.lstrip('.')
|
|
82
|
+
if (
|
|
83
|
+
host == entry
|
|
84
|
+
or host.endswith('.' + entry)
|
|
85
|
+
or host_and_port == entry
|
|
86
|
+
or host_and_port.endswith('.' + entry)
|
|
87
|
+
):
|
|
88
|
+
return True
|
|
89
|
+
|
|
90
|
+
return False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class RESTResponse(io.IOBase):
|
|
94
|
+
|
|
95
|
+
def __init__(self, resp) -> None:
|
|
96
|
+
self.response = resp
|
|
97
|
+
self.status = resp.status
|
|
98
|
+
self.reason = resp.reason
|
|
99
|
+
self.data = None
|
|
100
|
+
|
|
101
|
+
def read(self):
|
|
102
|
+
if self.data is None:
|
|
103
|
+
self.data = self.response.data
|
|
104
|
+
return self.data
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def headers(self):
|
|
108
|
+
"""Returns a dictionary of response headers."""
|
|
109
|
+
return self.response.headers
|
|
110
|
+
|
|
111
|
+
def getheaders(self):
|
|
112
|
+
"""Returns a dictionary of the response headers; use ``headers`` instead."""
|
|
113
|
+
return self.response.headers
|
|
114
|
+
|
|
115
|
+
def getheader(self, name, default=None):
|
|
116
|
+
"""Returns a given response header; use ``headers.get()`` instead."""
|
|
117
|
+
return self.response.headers.get(name, default)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class RESTClientObject:
|
|
121
|
+
|
|
122
|
+
def __init__(self, configuration) -> None:
|
|
123
|
+
# urllib3.PoolManager will pass all kw parameters to connectionpool
|
|
124
|
+
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501
|
|
125
|
+
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501
|
|
126
|
+
# Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501
|
|
127
|
+
|
|
128
|
+
# cert_reqs
|
|
129
|
+
if configuration.verify_ssl:
|
|
130
|
+
cert_reqs = ssl.CERT_REQUIRED
|
|
131
|
+
else:
|
|
132
|
+
cert_reqs = ssl.CERT_NONE
|
|
133
|
+
|
|
134
|
+
pool_args = {
|
|
135
|
+
"cert_reqs": cert_reqs,
|
|
136
|
+
"ca_certs": configuration.ssl_ca_cert,
|
|
137
|
+
"cert_file": configuration.cert_file,
|
|
138
|
+
"key_file": configuration.key_file,
|
|
139
|
+
"ca_cert_data": configuration.ca_cert_data,
|
|
140
|
+
}
|
|
141
|
+
if configuration.assert_hostname is not None:
|
|
142
|
+
pool_args['assert_hostname'] = (
|
|
143
|
+
configuration.assert_hostname
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
if configuration.retries is not None:
|
|
147
|
+
pool_args['retries'] = configuration.retries
|
|
148
|
+
|
|
149
|
+
if configuration.tls_server_name:
|
|
150
|
+
pool_args['server_hostname'] = configuration.tls_server_name
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if configuration.socket_options is not None:
|
|
154
|
+
pool_args['socket_options'] = configuration.socket_options
|
|
155
|
+
|
|
156
|
+
if configuration.connection_pool_maxsize is not None:
|
|
157
|
+
pool_args['maxsize'] = configuration.connection_pool_maxsize
|
|
158
|
+
|
|
159
|
+
# https pool manager
|
|
160
|
+
self.pool_manager: urllib3.PoolManager
|
|
161
|
+
|
|
162
|
+
if configuration.proxy and not should_bypass_proxies(
|
|
163
|
+
configuration.host, configuration.no_proxy or ''
|
|
164
|
+
):
|
|
165
|
+
if is_socks_proxy_url(configuration.proxy):
|
|
166
|
+
from urllib3.contrib.socks import SOCKSProxyManager
|
|
167
|
+
pool_args["proxy_url"] = configuration.proxy
|
|
168
|
+
pool_args["headers"] = configuration.proxy_headers
|
|
169
|
+
self.pool_manager = SOCKSProxyManager(**pool_args)
|
|
170
|
+
else:
|
|
171
|
+
pool_args["proxy_url"] = configuration.proxy
|
|
172
|
+
pool_args["proxy_headers"] = configuration.proxy_headers
|
|
173
|
+
if configuration.proxy_ssl_context is not None:
|
|
174
|
+
pool_args["proxy_ssl_context"] = configuration.proxy_ssl_context
|
|
175
|
+
self.pool_manager = urllib3.ProxyManager(**pool_args)
|
|
176
|
+
else:
|
|
177
|
+
self.pool_manager = urllib3.PoolManager(**pool_args)
|
|
178
|
+
|
|
179
|
+
def request(
|
|
180
|
+
self,
|
|
181
|
+
method,
|
|
182
|
+
url,
|
|
183
|
+
headers=None,
|
|
184
|
+
body=None,
|
|
185
|
+
post_params=None,
|
|
186
|
+
_request_timeout=None
|
|
187
|
+
):
|
|
188
|
+
"""Perform requests.
|
|
189
|
+
|
|
190
|
+
:param method: http request method
|
|
191
|
+
:param url: http request url
|
|
192
|
+
:param headers: http request headers
|
|
193
|
+
:param body: request json body, for `application/json`
|
|
194
|
+
:param post_params: request post parameters,
|
|
195
|
+
`application/x-www-form-urlencoded`
|
|
196
|
+
and `multipart/form-data`
|
|
197
|
+
:param _request_timeout: timeout setting for this request. If one
|
|
198
|
+
number provided, it will be total request
|
|
199
|
+
timeout. It can also be a pair (tuple) of
|
|
200
|
+
(connection, read) timeouts.
|
|
201
|
+
"""
|
|
202
|
+
method = method.upper()
|
|
203
|
+
assert method in [
|
|
204
|
+
'GET',
|
|
205
|
+
'HEAD',
|
|
206
|
+
'DELETE',
|
|
207
|
+
'POST',
|
|
208
|
+
'PUT',
|
|
209
|
+
'PATCH',
|
|
210
|
+
'OPTIONS'
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
if post_params and body:
|
|
214
|
+
raise ApiValueError(
|
|
215
|
+
"body parameter cannot be used with post_params parameter."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
post_params = post_params or {}
|
|
219
|
+
headers = headers or {}
|
|
220
|
+
|
|
221
|
+
timeout = None
|
|
222
|
+
if _request_timeout:
|
|
223
|
+
if isinstance(_request_timeout, (int, float)):
|
|
224
|
+
timeout = urllib3.Timeout(total=_request_timeout)
|
|
225
|
+
elif (
|
|
226
|
+
isinstance(_request_timeout, tuple)
|
|
227
|
+
and len(_request_timeout) == 2
|
|
228
|
+
):
|
|
229
|
+
timeout = urllib3.Timeout(
|
|
230
|
+
connect=_request_timeout[0],
|
|
231
|
+
read=_request_timeout[1]
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
try:
|
|
235
|
+
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
|
|
236
|
+
if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
|
|
237
|
+
|
|
238
|
+
content_type = headers.get('Content-Type')
|
|
239
|
+
is_json = (
|
|
240
|
+
not content_type
|
|
241
|
+
or contenttype_matches(content_type, 'application', 'json')
|
|
242
|
+
)
|
|
243
|
+
# JSON is valid YAML 1.2, so structured YAML bodies can use
|
|
244
|
+
# the existing JSON serializer:
|
|
245
|
+
# https://yaml.org/spec/1.2.2/#13-relation-to-json
|
|
246
|
+
is_structured_yaml = (
|
|
247
|
+
content_type
|
|
248
|
+
and (
|
|
249
|
+
contenttype_matches(content_type, 'application', 'yaml')
|
|
250
|
+
or contenttype_matches(content_type, 'text', 'yaml')
|
|
251
|
+
or contenttype_matches(content_type, 'text', 'x-yaml')
|
|
252
|
+
)
|
|
253
|
+
and not isinstance(body, (str, bytes))
|
|
254
|
+
)
|
|
255
|
+
if is_json or is_structured_yaml:
|
|
256
|
+
request_body = None
|
|
257
|
+
if body is not None:
|
|
258
|
+
request_body = json.dumps(body)
|
|
259
|
+
r = self.pool_manager.request(
|
|
260
|
+
method,
|
|
261
|
+
url,
|
|
262
|
+
body=request_body,
|
|
263
|
+
timeout=timeout,
|
|
264
|
+
headers=headers,
|
|
265
|
+
preload_content=False
|
|
266
|
+
)
|
|
267
|
+
elif contenttype_matches(content_type, 'application', 'x-www-form-urlencoded'):
|
|
268
|
+
r = self.pool_manager.request(
|
|
269
|
+
method,
|
|
270
|
+
url,
|
|
271
|
+
fields=post_params,
|
|
272
|
+
encode_multipart=False,
|
|
273
|
+
timeout=timeout,
|
|
274
|
+
headers=headers,
|
|
275
|
+
preload_content=False
|
|
276
|
+
)
|
|
277
|
+
elif contenttype_matches(content_type, 'multipart', 'form-data'):
|
|
278
|
+
# must del headers['Content-Type'], or the correct
|
|
279
|
+
# Content-Type which generated by urllib3 will be
|
|
280
|
+
# overwritten.
|
|
281
|
+
del headers['Content-Type']
|
|
282
|
+
# Ensures that dict objects are serialized
|
|
283
|
+
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
|
|
284
|
+
r = self.pool_manager.request(
|
|
285
|
+
method,
|
|
286
|
+
url,
|
|
287
|
+
fields=post_params,
|
|
288
|
+
encode_multipart=True,
|
|
289
|
+
timeout=timeout,
|
|
290
|
+
headers=headers,
|
|
291
|
+
preload_content=False
|
|
292
|
+
)
|
|
293
|
+
# Pass a `string` parameter directly in the body to support
|
|
294
|
+
# other content types than JSON when `body` argument is
|
|
295
|
+
# provided in serialized form.
|
|
296
|
+
elif isinstance(body, str) or isinstance(body, bytes):
|
|
297
|
+
r = self.pool_manager.request(
|
|
298
|
+
method,
|
|
299
|
+
url,
|
|
300
|
+
body=body,
|
|
301
|
+
timeout=timeout,
|
|
302
|
+
headers=headers,
|
|
303
|
+
preload_content=False
|
|
304
|
+
)
|
|
305
|
+
elif content_type.startswith('text/') and isinstance(body, bool):
|
|
306
|
+
request_body = "true" if body else "false"
|
|
307
|
+
r = self.pool_manager.request(
|
|
308
|
+
method,
|
|
309
|
+
url,
|
|
310
|
+
body=request_body,
|
|
311
|
+
preload_content=False,
|
|
312
|
+
timeout=timeout,
|
|
313
|
+
headers=headers)
|
|
314
|
+
else:
|
|
315
|
+
# Cannot generate the request from given parameters
|
|
316
|
+
msg = """Cannot prepare a request message for provided
|
|
317
|
+
arguments. Please check that your arguments match
|
|
318
|
+
declared content type."""
|
|
319
|
+
raise ApiException(status=0, reason=msg)
|
|
320
|
+
# For `GET`, `HEAD`
|
|
321
|
+
else:
|
|
322
|
+
r = self.pool_manager.request(
|
|
323
|
+
method,
|
|
324
|
+
url,
|
|
325
|
+
fields={},
|
|
326
|
+
timeout=timeout,
|
|
327
|
+
headers=headers,
|
|
328
|
+
preload_content=False
|
|
329
|
+
)
|
|
330
|
+
except urllib3.exceptions.SSLError as e:
|
|
331
|
+
msg = "\n".join([type(e).__name__, str(e)])
|
|
332
|
+
raise ApiException(status=0, reason=msg)
|
|
333
|
+
|
|
334
|
+
return RESTResponse(r)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: victorycode_sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Victory Code Client API
|
|
5
|
+
Home-page: https://github.com/victory-code-ai/victorycode-sdks
|
|
6
|
+
Author: Victory Code
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Project-URL: Repository, https://github.com/victory-code-ai/victorycode-sdks
|
|
9
|
+
Keywords: OpenAPI,OpenAPI-Generator,Victory Code Client API
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: urllib3<3.0.0,>=2.7.0
|
|
14
|
+
Requires-Dist: python-dateutil>=2.8.2
|
|
15
|
+
Requires-Dist: pydantic>=2.11
|
|
16
|
+
Requires-Dist: typing-extensions>=4.7.1
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# victorycode-sdk
|
|
21
|
+
Client APIs for Tactix partners: authenticate, upload game video, and retrieve games, plays, game recaps and team metadata.
|
|
22
|
+
|
|
23
|
+
This Python package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
|
24
|
+
|
|
25
|
+
- API version: 1.0
|
|
26
|
+
- Package version: 1.0.0
|
|
27
|
+
- Generator version: 7.25.0
|
|
28
|
+
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
|
|
29
|
+
|
|
30
|
+
## Requirements.
|
|
31
|
+
|
|
32
|
+
Python 3.10+
|
|
33
|
+
|
|
34
|
+
## Installation & Usage
|
|
35
|
+
### pip install
|
|
36
|
+
|
|
37
|
+
If the python package is hosted on a repository, you can install directly using:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
pip install git+https://github.com/victory-code-ai/victorycode-sdks.git
|
|
41
|
+
```
|
|
42
|
+
(you may need to run `pip` with root permission: `sudo pip install git+https://github.com/victory-code-ai/victorycode-sdks.git`)
|
|
43
|
+
|
|
44
|
+
Then import the package:
|
|
45
|
+
```python
|
|
46
|
+
import victorycode_sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Setuptools
|
|
50
|
+
|
|
51
|
+
Install via [Setuptools](http://pypi.python.org/pypi/setuptools).
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
python setup.py install --user
|
|
55
|
+
```
|
|
56
|
+
(or `sudo python setup.py install` to install the package for all users)
|
|
57
|
+
|
|
58
|
+
Then import the package:
|
|
59
|
+
```python
|
|
60
|
+
import victorycode_sdk
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Tests
|
|
64
|
+
|
|
65
|
+
Execute `pytest` to run the tests.
|
|
66
|
+
|
|
67
|
+
## Getting Started
|
|
68
|
+
|
|
69
|
+
Please follow the [installation procedure](#installation--usage) and then run the following:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
|
|
73
|
+
import victorycode_sdk
|
|
74
|
+
from victorycode_sdk.rest import ApiException
|
|
75
|
+
from pprint import pprint
|
|
76
|
+
|
|
77
|
+
# Defining the host is optional and defaults to https://sandbox.api.tactixai.com
|
|
78
|
+
# See configuration.py for a list of all supported configuration parameters.
|
|
79
|
+
configuration = victorycode_sdk.Configuration(
|
|
80
|
+
host = "https://sandbox.api.tactixai.com"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# The client must configure the authentication and authorization parameters
|
|
84
|
+
# in accordance with the API server security policy.
|
|
85
|
+
# Examples for each auth method are provided below, use the example that
|
|
86
|
+
# satisfies your auth use case.
|
|
87
|
+
|
|
88
|
+
# Configure API key authorization: AppSecret
|
|
89
|
+
configuration.api_key['AppSecret'] = os.environ["API_KEY"]
|
|
90
|
+
|
|
91
|
+
# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
92
|
+
# configuration.api_key_prefix['AppSecret'] = 'Bearer'
|
|
93
|
+
|
|
94
|
+
# Configure API key authorization: Client-App-Id
|
|
95
|
+
configuration.api_key['Client-App-Id'] = os.environ["API_KEY"]
|
|
96
|
+
|
|
97
|
+
# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
98
|
+
# configuration.api_key_prefix['Client-App-Id'] = 'Bearer'
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Enter a context with an instance of the API client
|
|
102
|
+
with victorycode_sdk.ApiClient(configuration) as api_client:
|
|
103
|
+
# Create an instance of the API class
|
|
104
|
+
api_instance = victorycode_sdk.AuthenticationApi(api_client)
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
# Get Access Token
|
|
108
|
+
api_response = api_instance.get_access_token()
|
|
109
|
+
print("The response of AuthenticationApi->get_access_token:\n")
|
|
110
|
+
pprint(api_response)
|
|
111
|
+
except ApiException as e:
|
|
112
|
+
print("Exception when calling AuthenticationApi->get_access_token: %s\n" % e)
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Documentation for API Endpoints
|
|
117
|
+
|
|
118
|
+
All URIs are relative to *https://sandbox.api.tactixai.com*
|
|
119
|
+
|
|
120
|
+
Class | Method | HTTP request | Description
|
|
121
|
+
------------ | ------------- | ------------- | -------------
|
|
122
|
+
*AuthenticationApi* | [**get_access_token**](docs/AuthenticationApi.md#get_access_token) | **GET** /api/v1/client/auth/token | Get Access Token
|
|
123
|
+
*ClassificationApi* | [**create_classification**](docs/ClassificationApi.md#create_classification) | **POST** /api/v1/client/classifications | Create a new classification
|
|
124
|
+
*ClassificationApi* | [**delete_classification**](docs/ClassificationApi.md#delete_classification) | **DELETE** /api/v1/client/classifications/{id} | Delete a classification
|
|
125
|
+
*ClassificationApi* | [**get_classifications**](docs/ClassificationApi.md#get_classifications) | **GET** /api/v1/client/classifications | Get all classifications
|
|
126
|
+
*ClassificationApi* | [**update_classification**](docs/ClassificationApi.md#update_classification) | **PATCH** /api/v1/client/classifications/{id} | Update a classification
|
|
127
|
+
*GameRecapApi* | [**get_game_recap_game_box_score**](docs/GameRecapApi.md#get_game_recap_game_box_score) | **GET** /api/v1/client/game-recap/{videoId}/game-box-score | Get the Game Recap Game Box Score for a specific Game.
|
|
128
|
+
*GameRecapApi* | [**get_game_recap_score**](docs/GameRecapApi.md#get_game_recap_score) | **GET** /api/v1/client/game-recap/{videoId}/score | Get the Game Recap Score for a specific Game.
|
|
129
|
+
*GameRecapApi* | [**get_game_recap_scoring_summary**](docs/GameRecapApi.md#get_game_recap_scoring_summary) | **GET** /api/v1/client/game-recap/{videoId}/scoring-summary | Get the Game Recap Scoring Summary for a specific Game.
|
|
130
|
+
*GameRecapApi* | [**get_game_recap_scoring_summary_pro**](docs/GameRecapApi.md#get_game_recap_scoring_summary_pro) | **GET** /api/v1/client/game-recap/{videoId}/scoring-summary-pro | Get the Game Recap Scoring Summary Pro for a specific Game.
|
|
131
|
+
*GameRecapApi* | [**get_game_recap_team_stats**](docs/GameRecapApi.md#get_game_recap_team_stats) | **GET** /api/v1/client/game-recap/{videoId}/team-stats | Get the Game Recap Team Stats for a specific Game.
|
|
132
|
+
*GamesApi* | [**add_video_to_game**](docs/GamesApi.md#add_video_to_game) | **POST** /api/v1/client/games/{gameId}/video | Add new video to a game
|
|
133
|
+
*GamesApi* | [**create_game_with_video_url**](docs/GamesApi.md#create_game_with_video_url) | **POST** /api/v1/client/games | Create a new Game with Video URL
|
|
134
|
+
*GamesApi* | [**get_game_details**](docs/GamesApi.md#get_game_details) | **GET** /api/v1/client/games/{gameId} | Get a Single Game.
|
|
135
|
+
*GamesApi* | [**get_games**](docs/GamesApi.md#get_games) | **GET** /api/v1/client/games | List and Filter Games.
|
|
136
|
+
*GamesApi* | [**get_videos_of_game**](docs/GamesApi.md#get_videos_of_game) | **GET** /api/v1/client/games/{gameId}/videos | Get a list of videos of a game
|
|
137
|
+
*MascotApi* | [**create_mascot**](docs/MascotApi.md#create_mascot) | **POST** /api/v1/client/mascots | Create a new mascot
|
|
138
|
+
*MascotApi* | [**delete_mascot**](docs/MascotApi.md#delete_mascot) | **DELETE** /api/v1/client/mascots/{id} | Delete a mascot
|
|
139
|
+
*MascotApi* | [**get_mascots**](docs/MascotApi.md#get_mascots) | **GET** /api/v1/client/mascots | Get all mascots
|
|
140
|
+
*MascotApi* | [**update_mascot**](docs/MascotApi.md#update_mascot) | **PATCH** /api/v1/client/mascots/{id} | Update a mascot
|
|
141
|
+
*PlaysEventsApi* | [**get_play_by_id**](docs/PlaysEventsApi.md#get_play_by_id) | **GET** /api/v1/client/plays/{playId} | Get the single Play Clip
|
|
142
|
+
*PlaysEventsApi* | [**get_plays_of_game**](docs/PlaysEventsApi.md#get_plays_of_game) | **GET** /api/v1/client/game/{gameId}/plays | Get a list of all plays for a game
|
|
143
|
+
*PlaysEventsApi* | [**get_plays_of_video**](docs/PlaysEventsApi.md#get_plays_of_video) | **GET** /api/v1/client/videos/{videoId}/plays | Get a list of play clips for a video
|
|
144
|
+
*TeamsApi* | [**create_team**](docs/TeamsApi.md#create_team) | **POST** /api/v1/client/teams | Create a new team
|
|
145
|
+
*TeamsApi* | [**delete_team**](docs/TeamsApi.md#delete_team) | **DELETE** /api/v1/client/teams/{id} | Delete a team
|
|
146
|
+
*TeamsApi* | [**get_teams**](docs/TeamsApi.md#get_teams) | **GET** /api/v1/client/teams | Get teams
|
|
147
|
+
*TeamsApi* | [**update_team**](docs/TeamsApi.md#update_team) | **PATCH** /api/v1/client/teams/{id} | Update a team
|
|
148
|
+
*UploadsApi* | [**complete_multipart_upload**](docs/UploadsApi.md#complete_multipart_upload) | **POST** /api/v1/client/complete-upload | Complete a multipart upload to S3
|
|
149
|
+
*UploadsApi* | [**get_presigned_url**](docs/UploadsApi.md#get_presigned_url) | **GET** /api/v1/client/upload-presigned-url | Get a presigned URL for a specific part of a multipart upload
|
|
150
|
+
*UploadsApi* | [**initiate_upload**](docs/UploadsApi.md#initiate_upload) | **POST** /api/v1/client/initiate-upload | Initiate a multipart upload to S3 for a large file
|
|
151
|
+
*VenueApi* | [**create_venue**](docs/VenueApi.md#create_venue) | **POST** /api/v1/client/venue | Create a new venue
|
|
152
|
+
*VenueApi* | [**delete_venue**](docs/VenueApi.md#delete_venue) | **DELETE** /api/v1/client/venue/{id} | Delete a venue
|
|
153
|
+
*VenueApi* | [**get_venues**](docs/VenueApi.md#get_venues) | **GET** /api/v1/client/venues | Get all venues
|
|
154
|
+
*VenueApi* | [**update_venue**](docs/VenueApi.md#update_venue) | **PATCH** /api/v1/client/venue/{id} | Update a venue
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
## Documentation For Models
|
|
158
|
+
|
|
159
|
+
- [AddVideoToGameWithS3LinkDto](docs/AddVideoToGameWithS3LinkDto.md)
|
|
160
|
+
- [Address](docs/Address.md)
|
|
161
|
+
- [AddressDto](docs/AddressDto.md)
|
|
162
|
+
- [BadRequestErrorResponseDto](docs/BadRequestErrorResponseDto.md)
|
|
163
|
+
- [Classification](docs/Classification.md)
|
|
164
|
+
- [ClientCreateGameWithVideoUrlDto](docs/ClientCreateGameWithVideoUrlDto.md)
|
|
165
|
+
- [CompleteMultipartUploadDto](docs/CompleteMultipartUploadDto.md)
|
|
166
|
+
- [CompleteMultipartUploadResponse](docs/CompleteMultipartUploadResponse.md)
|
|
167
|
+
- [CompleteMultipartUploadResponseDto](docs/CompleteMultipartUploadResponseDto.md)
|
|
168
|
+
- [CompletedPartDto](docs/CompletedPartDto.md)
|
|
169
|
+
- [ConflictErrorResponseDto](docs/ConflictErrorResponseDto.md)
|
|
170
|
+
- [Coordinates](docs/Coordinates.md)
|
|
171
|
+
- [CoordinatesDto](docs/CoordinatesDto.md)
|
|
172
|
+
- [CreateClassificationDto](docs/CreateClassificationDto.md)
|
|
173
|
+
- [CreateMascotDto](docs/CreateMascotDto.md)
|
|
174
|
+
- [CreateTeamDto](docs/CreateTeamDto.md)
|
|
175
|
+
- [CreateTeamResponseDto](docs/CreateTeamResponseDto.md)
|
|
176
|
+
- [CreateVenueDto](docs/CreateVenueDto.md)
|
|
177
|
+
- [DefenseSectionDto](docs/DefenseSectionDto.md)
|
|
178
|
+
- [DefenseStatsDto](docs/DefenseStatsDto.md)
|
|
179
|
+
- [DefenseTotalsDto](docs/DefenseTotalsDto.md)
|
|
180
|
+
- [DeleteResponseDto](docs/DeleteResponseDto.md)
|
|
181
|
+
- [DownEfficiencyDto](docs/DownEfficiencyDto.md)
|
|
182
|
+
- [FetchTeamResponseDto](docs/FetchTeamResponseDto.md)
|
|
183
|
+
- [FumbleSectionDto](docs/FumbleSectionDto.md)
|
|
184
|
+
- [FumbleStatsDto](docs/FumbleStatsDto.md)
|
|
185
|
+
- [FumbleTotalsDto](docs/FumbleTotalsDto.md)
|
|
186
|
+
- [GameAllPlayListItemDto](docs/GameAllPlayListItemDto.md)
|
|
187
|
+
- [GameBoxScoreMetaDto](docs/GameBoxScoreMetaDto.md)
|
|
188
|
+
- [GameBoxScoreResponseDto](docs/GameBoxScoreResponseDto.md)
|
|
189
|
+
- [GameBoxScoreTeamsDto](docs/GameBoxScoreTeamsDto.md)
|
|
190
|
+
- [GameDetailsResponse](docs/GameDetailsResponse.md)
|
|
191
|
+
- [GameRecapScoringSummaryProResponse](docs/GameRecapScoringSummaryProResponse.md)
|
|
192
|
+
- [GameRecapScoringSummaryResponse](docs/GameRecapScoringSummaryResponse.md)
|
|
193
|
+
- [GameRecapTeamStatsResponse](docs/GameRecapTeamStatsResponse.md)
|
|
194
|
+
- [GameScoreResponse](docs/GameScoreResponse.md)
|
|
195
|
+
- [GameScoringSummaryDataDto](docs/GameScoringSummaryDataDto.md)
|
|
196
|
+
- [GetPartsPresignUrlResponse](docs/GetPartsPresignUrlResponse.md)
|
|
197
|
+
- [GetPartsPresignUrlResponseDto](docs/GetPartsPresignUrlResponseDto.md)
|
|
198
|
+
- [InitiateMultipartUploadDto](docs/InitiateMultipartUploadDto.md)
|
|
199
|
+
- [InitiateMultipartUploadResponse](docs/InitiateMultipartUploadResponse.md)
|
|
200
|
+
- [InitiateMultipartUploadResponseDto](docs/InitiateMultipartUploadResponseDto.md)
|
|
201
|
+
- [InterceptionSectionDto](docs/InterceptionSectionDto.md)
|
|
202
|
+
- [InterceptionStatsDto](docs/InterceptionStatsDto.md)
|
|
203
|
+
- [InterceptionTotalsDto](docs/InterceptionTotalsDto.md)
|
|
204
|
+
- [KickingSectionDto](docs/KickingSectionDto.md)
|
|
205
|
+
- [KickingStatsDto](docs/KickingStatsDto.md)
|
|
206
|
+
- [KickingTotalsDto](docs/KickingTotalsDto.md)
|
|
207
|
+
- [LineOfScrimmageDto](docs/LineOfScrimmageDto.md)
|
|
208
|
+
- [ListClassificationPaginatedResponseDto](docs/ListClassificationPaginatedResponseDto.md)
|
|
209
|
+
- [ListGameAllPlaysResponseDto](docs/ListGameAllPlaysResponseDto.md)
|
|
210
|
+
- [ListGamesPaginatedResponseDto](docs/ListGamesPaginatedResponseDto.md)
|
|
211
|
+
- [ListMascotPaginatedResponseDto](docs/ListMascotPaginatedResponseDto.md)
|
|
212
|
+
- [ListPlayClipsResponseDto](docs/ListPlayClipsResponseDto.md)
|
|
213
|
+
- [ListTeamPaginatedResponseDto](docs/ListTeamPaginatedResponseDto.md)
|
|
214
|
+
- [ListVenuePaginatedResponseDto](docs/ListVenuePaginatedResponseDto.md)
|
|
215
|
+
- [ListVideoPaginatedResponseDto](docs/ListVideoPaginatedResponseDto.md)
|
|
216
|
+
- [Mascot](docs/Mascot.md)
|
|
217
|
+
- [MascotResponseDto](docs/MascotResponseDto.md)
|
|
218
|
+
- [NotFoundErrorResponseDto](docs/NotFoundErrorResponseDto.md)
|
|
219
|
+
- [OffenseDto](docs/OffenseDto.md)
|
|
220
|
+
- [PassingDto](docs/PassingDto.md)
|
|
221
|
+
- [PassingSectionDto](docs/PassingSectionDto.md)
|
|
222
|
+
- [PassingStatsDto](docs/PassingStatsDto.md)
|
|
223
|
+
- [PassingTotalsDto](docs/PassingTotalsDto.md)
|
|
224
|
+
- [PeriodScoreDto](docs/PeriodScoreDto.md)
|
|
225
|
+
- [PlayAttributesListDto](docs/PlayAttributesListDto.md)
|
|
226
|
+
- [PlayClipListDetailsDto](docs/PlayClipListDetailsDto.md)
|
|
227
|
+
- [PlayClipListItemDto](docs/PlayClipListItemDto.md)
|
|
228
|
+
- [ProScoringPlayDto](docs/ProScoringPlayDto.md)
|
|
229
|
+
- [ReceivingSectionDto](docs/ReceivingSectionDto.md)
|
|
230
|
+
- [ReceivingStatsDto](docs/ReceivingStatsDto.md)
|
|
231
|
+
- [ReceivingTotalsDto](docs/ReceivingTotalsDto.md)
|
|
232
|
+
- [RedZoneDto](docs/RedZoneDto.md)
|
|
233
|
+
- [RushingDto](docs/RushingDto.md)
|
|
234
|
+
- [RushingSectionDto](docs/RushingSectionDto.md)
|
|
235
|
+
- [RushingStatsDto](docs/RushingStatsDto.md)
|
|
236
|
+
- [RushingTotalsDto](docs/RushingTotalsDto.md)
|
|
237
|
+
- [ScoringPlayPlayerInvolvedDto](docs/ScoringPlayPlayerInvolvedDto.md)
|
|
238
|
+
- [ScoringSummaryProTeamDto](docs/ScoringSummaryProTeamDto.md)
|
|
239
|
+
- [SingleClassificationResponseDto](docs/SingleClassificationResponseDto.md)
|
|
240
|
+
- [SingleMascotResponseDto](docs/SingleMascotResponseDto.md)
|
|
241
|
+
- [SingleTeamResponseDto](docs/SingleTeamResponseDto.md)
|
|
242
|
+
- [SingleVenueResponseDto](docs/SingleVenueResponseDto.md)
|
|
243
|
+
- [SingleVideoResponseDto](docs/SingleVideoResponseDto.md)
|
|
244
|
+
- [TeamBasicInfoDto](docs/TeamBasicInfoDto.md)
|
|
245
|
+
- [TeamBoxScoreDto](docs/TeamBoxScoreDto.md)
|
|
246
|
+
- [TeamScoreDto](docs/TeamScoreDto.md)
|
|
247
|
+
- [TeamStatsDto](docs/TeamStatsDto.md)
|
|
248
|
+
- [TokenResponse](docs/TokenResponse.md)
|
|
249
|
+
- [TurnoversDto](docs/TurnoversDto.md)
|
|
250
|
+
- [UnauthorizedErrorResponseDto](docs/UnauthorizedErrorResponseDto.md)
|
|
251
|
+
- [UpdateClassificationDto](docs/UpdateClassificationDto.md)
|
|
252
|
+
- [UpdateMascotDto](docs/UpdateMascotDto.md)
|
|
253
|
+
- [UpdateTeamDto](docs/UpdateTeamDto.md)
|
|
254
|
+
- [UpdateVenueDto](docs/UpdateVenueDto.md)
|
|
255
|
+
- [Venue](docs/Venue.md)
|
|
256
|
+
- [VideoListItemDto](docs/VideoListItemDto.md)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
<a id="documentation-for-authorization"></a>
|
|
260
|
+
## Documentation For Authorization
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
Authentication schemes defined for the API:
|
|
264
|
+
<a id="Client-App-Id"></a>
|
|
265
|
+
### Client-App-Id
|
|
266
|
+
|
|
267
|
+
- **Type**: API key
|
|
268
|
+
- **API key parameter name**: App-Id
|
|
269
|
+
- **Location**: HTTP header
|
|
270
|
+
|
|
271
|
+
<a id="Client-App-Token"></a>
|
|
272
|
+
### Client-App-Token
|
|
273
|
+
|
|
274
|
+
- **Type**: Bearer authentication (JWT)
|
|
275
|
+
|
|
276
|
+
<a id="AppSecret"></a>
|
|
277
|
+
### AppSecret
|
|
278
|
+
|
|
279
|
+
- **Type**: API key
|
|
280
|
+
- **API key parameter name**: App-Secret
|
|
281
|
+
- **Location**: HTTP header
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
## Author
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|