opengamedata-api-utils 1.0.0__tar.gz
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.
- opengamedata_api_utils-1.0.0/LICENSE +21 -0
- opengamedata_api_utils-1.0.0/PKG-INFO +73 -0
- opengamedata_api_utils-1.0.0/README.md +58 -0
- opengamedata_api_utils-1.0.0/pyproject.toml +28 -0
- opengamedata_api_utils-1.0.0/setup.cfg +4 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/__init__.py +9 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/schemas/ServerConfigSchema.py +148 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/schemas/__init__.py +0 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/utils/APIResponse.py +164 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/utils/APIUtils.py +92 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/utils/HelloAPI.py +73 -0
- opengamedata_api_utils-1.0.0/src/ogd/apis/utils/__init__.py +0 -0
- opengamedata_api_utils-1.0.0/src/opengamedata_api_utils.egg-info/PKG-INFO +73 -0
- opengamedata_api_utils-1.0.0/src/opengamedata_api_utils.egg-info/SOURCES.txt +14 -0
- opengamedata_api_utils-1.0.0/src/opengamedata_api_utils.egg-info/dependency_links.txt +1 -0
- opengamedata_api_utils-1.0.0/src/opengamedata_api_utils.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 opengamedata
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: opengamedata-api-utils
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Package of utilities for server-side scripts in OpenGameData.
|
|
5
|
+
Author: Ryan Wilkinson, Glenn Palmer, Daus Husaini
|
|
6
|
+
Author-email: Luke Swanson <superscription58@gmail.com>
|
|
7
|
+
Project-URL: Homepage, https://github.com/opengamedata/opengamedata-api-utils
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/opengamedata/opengamedata-api-utils/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# opengamedata-server
|
|
17
|
+
|
|
18
|
+
Repository for server-side scripts in opengamedata. In particular, this is where we have code for the OGD APIs.
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
### Hello World of Flask
|
|
23
|
+
|
|
24
|
+
Steps to run:
|
|
25
|
+
1. Check out latest `opengamedata-server`.
|
|
26
|
+
2. Run `pip install -r requirements.txt` to ensure you've got flask.
|
|
27
|
+
3. Run `flask run`.
|
|
28
|
+
4. Open localhost:5000 or localhost:5000/hello to see some really basic text output from the Flask server.
|
|
29
|
+
|
|
30
|
+
If Flask doesn't run, it's possible you'd need to first export FLASK_APP as an environment variable, set to "wsgi" (so in Bash, export FLASK_APP=wsgi).
|
|
31
|
+
However, the script is named wsgi.py specifically because Flask is supposed to auto-detect it. So if this issue ever did come up, please ping Luke so he can look into it.
|
|
32
|
+
|
|
33
|
+
## APIs
|
|
34
|
+
|
|
35
|
+
Below is a listing of the current API calls available, in function format to indicate what the request parameters.
|
|
36
|
+
For each API, there is also an api path, with path parameters in angle bracket (<, >) format.
|
|
37
|
+
Lastly, at this point in time, the `<server_path>` is `https://fieldday-web.wcer.wisc.edu/opengamedata.wsgi`
|
|
38
|
+
|
|
39
|
+
### Classroom API
|
|
40
|
+
|
|
41
|
+
### Dashboard API
|
|
42
|
+
|
|
43
|
+
### Hello API
|
|
44
|
+
|
|
45
|
+
#### Verify the API is alive
|
|
46
|
+
|
|
47
|
+
`<server_path>/hello`
|
|
48
|
+
`GET()`
|
|
49
|
+
|
|
50
|
+
- returns no value, and a success message
|
|
51
|
+
|
|
52
|
+
`POST()`
|
|
53
|
+
|
|
54
|
+
- returns no value, and a success message
|
|
55
|
+
|
|
56
|
+
`PUT()`
|
|
57
|
+
|
|
58
|
+
- returns no value, and a success message
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
### Player API
|
|
62
|
+
|
|
63
|
+
### Generate and save players
|
|
64
|
+
|
|
65
|
+
`<server_path>/player`
|
|
66
|
+
`GET()`
|
|
67
|
+
|
|
68
|
+
- returns an unused, randomized player name, or a null value and error message
|
|
69
|
+
|
|
70
|
+
`PUT(str player_id, str name = None)`
|
|
71
|
+
|
|
72
|
+
- Returns no value, and either a success or error message
|
|
73
|
+
---
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# opengamedata-server
|
|
2
|
+
|
|
3
|
+
Repository for server-side scripts in opengamedata. In particular, this is where we have code for the OGD APIs.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
### Hello World of Flask
|
|
8
|
+
|
|
9
|
+
Steps to run:
|
|
10
|
+
1. Check out latest `opengamedata-server`.
|
|
11
|
+
2. Run `pip install -r requirements.txt` to ensure you've got flask.
|
|
12
|
+
3. Run `flask run`.
|
|
13
|
+
4. Open localhost:5000 or localhost:5000/hello to see some really basic text output from the Flask server.
|
|
14
|
+
|
|
15
|
+
If Flask doesn't run, it's possible you'd need to first export FLASK_APP as an environment variable, set to "wsgi" (so in Bash, export FLASK_APP=wsgi).
|
|
16
|
+
However, the script is named wsgi.py specifically because Flask is supposed to auto-detect it. So if this issue ever did come up, please ping Luke so he can look into it.
|
|
17
|
+
|
|
18
|
+
## APIs
|
|
19
|
+
|
|
20
|
+
Below is a listing of the current API calls available, in function format to indicate what the request parameters.
|
|
21
|
+
For each API, there is also an api path, with path parameters in angle bracket (<, >) format.
|
|
22
|
+
Lastly, at this point in time, the `<server_path>` is `https://fieldday-web.wcer.wisc.edu/opengamedata.wsgi`
|
|
23
|
+
|
|
24
|
+
### Classroom API
|
|
25
|
+
|
|
26
|
+
### Dashboard API
|
|
27
|
+
|
|
28
|
+
### Hello API
|
|
29
|
+
|
|
30
|
+
#### Verify the API is alive
|
|
31
|
+
|
|
32
|
+
`<server_path>/hello`
|
|
33
|
+
`GET()`
|
|
34
|
+
|
|
35
|
+
- returns no value, and a success message
|
|
36
|
+
|
|
37
|
+
`POST()`
|
|
38
|
+
|
|
39
|
+
- returns no value, and a success message
|
|
40
|
+
|
|
41
|
+
`PUT()`
|
|
42
|
+
|
|
43
|
+
- returns no value, and a success message
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
### Player API
|
|
47
|
+
|
|
48
|
+
### Generate and save players
|
|
49
|
+
|
|
50
|
+
`<server_path>/player`
|
|
51
|
+
`GET()`
|
|
52
|
+
|
|
53
|
+
- returns an unused, randomized player name, or a null value and error message
|
|
54
|
+
|
|
55
|
+
`PUT(str player_id, str name = None)`
|
|
56
|
+
|
|
57
|
+
- Returns no value, and either a success or error message
|
|
58
|
+
---
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools >= 61.0", "wheel", "setuptools-git-versioning >= 1.13"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[tool.setuptools-git-versioning]
|
|
6
|
+
enabled = true
|
|
7
|
+
|
|
8
|
+
[project]
|
|
9
|
+
name = "opengamedata-api-utils"
|
|
10
|
+
dynamic = ["version"]
|
|
11
|
+
authors = [
|
|
12
|
+
{ name="Luke Swanson", email="superscription58@gmail.com" },
|
|
13
|
+
{ name="Ryan Wilkinson" },
|
|
14
|
+
{ name="Glenn Palmer" },
|
|
15
|
+
{ name="Daus Husaini" }
|
|
16
|
+
]
|
|
17
|
+
description = "Package of utilities for server-side scripts in OpenGameData."
|
|
18
|
+
readme = "README.md"
|
|
19
|
+
requires-python = ">=3.10"
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: OS Independent",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
"Homepage" = "https://github.com/opengamedata/opengamedata-api-utils"
|
|
28
|
+
"Bug Tracker" = "https://github.com/opengamedata/opengamedata-api-utils/issues"
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ServerConfigSchema
|
|
3
|
+
|
|
4
|
+
Contains a Schema class for managing config data for server configurations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# import standard libraries
|
|
8
|
+
import logging
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Dict, List
|
|
11
|
+
|
|
12
|
+
# import 3rd-party libraries
|
|
13
|
+
|
|
14
|
+
# import OGD libraries
|
|
15
|
+
from ogd.core.schemas.Schema import Schema
|
|
16
|
+
from ogd.core.schemas.configs.data_sources.MySQLSourceSchema import MySQLSchema
|
|
17
|
+
|
|
18
|
+
# import local files
|
|
19
|
+
|
|
20
|
+
class ServerConfigSchema(Schema):
|
|
21
|
+
def __init__(self, name:str, all_elements:Dict[str, Any], logger:logging.Logger):
|
|
22
|
+
self._state_dbs : Dict[str, MySQLSchema]
|
|
23
|
+
self._ogd_core : Path
|
|
24
|
+
self._google_client_id : str
|
|
25
|
+
self._dbg_level : int
|
|
26
|
+
self._version : int
|
|
27
|
+
|
|
28
|
+
if "DB_CONFIG" in all_elements.keys():
|
|
29
|
+
self._data_src = ServerConfigSchema._parseDataSources(all_elements["DB_CONFIG"], logger=logger)
|
|
30
|
+
else:
|
|
31
|
+
self._data_src = {}
|
|
32
|
+
logger.warn(f"{name} config does not have a 'DB_CONFIG' element; defaulting to game_sources={self._data_src}", logging.WARN)
|
|
33
|
+
if "OGD_CORE_PATH" in all_elements.keys():
|
|
34
|
+
self._ogd_core = ServerConfigSchema._parseOGDPath(path=all_elements["OGD_CORE_PATH"], logger=logger)
|
|
35
|
+
else:
|
|
36
|
+
self._ogd_core = Path("./") / "opengamedata"
|
|
37
|
+
logger.warn(f"{name} config does not have a 'OGD_CORE_PATH' element; defaulting to ogd_core_path={self._ogd_core}", logging.WARN)
|
|
38
|
+
if "GOOGLE_CLIENT_ID" in all_elements.keys():
|
|
39
|
+
self._google_client_id = ServerConfigSchema._parseGoogleID(google_id=all_elements["GOOGLE_CLIENT_ID"], logger=logger)
|
|
40
|
+
else:
|
|
41
|
+
self._google_client_id = "UNKNOWN ID"
|
|
42
|
+
logger.warn(f"{name} config does not have a 'GOOGLE_CLIENT_ID' element; defaulting to google_client_id={self._google_client_id}", logging.WARN)
|
|
43
|
+
if "DEBUG_LEVEL" in all_elements.keys():
|
|
44
|
+
self._dbg_level = ServerConfigSchema._parseDebugLevel(all_elements["DEBUG_LEVEL"], logger=logger)
|
|
45
|
+
else:
|
|
46
|
+
self._dbg_level = logging.INFO
|
|
47
|
+
logger.warn(f"{name} config does not have a 'DEBUG_LEVEL' element; defaulting to dbg_level={self._dbg_level}", logging.WARN)
|
|
48
|
+
if "VER" in all_elements.keys():
|
|
49
|
+
self._version = ServerConfigSchema._parseVersion(all_elements["VER"], logger=logger)
|
|
50
|
+
else:
|
|
51
|
+
self._version = -1
|
|
52
|
+
logger.warn(f"{name} config does not have a 'VER' element; defaulting to version={self._version}", logging.WARN)
|
|
53
|
+
|
|
54
|
+
_used = {"DB_CONFIG", "OGD_CORE_PATH", "GOOGLE_CLIENT_ID", "DEBUG_LEVEL", "VER"}
|
|
55
|
+
_leftovers = { key : val for key,val in all_elements.items() if key not in _used }
|
|
56
|
+
super().__init__(name=name, other_elements=_leftovers)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def StateDatabases(self) -> Dict[str, MySQLSchema]:
|
|
60
|
+
return self._state_dbs
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def OGDCore(self) -> Path:
|
|
64
|
+
return self._ogd_core
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def GoogleClientID(self) -> str:
|
|
68
|
+
return self._google_client_id
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def DebugLevel(self) -> int:
|
|
72
|
+
return self._dbg_level
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def Version(self) -> int:
|
|
76
|
+
return self._version
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def AsMarkdown(self) -> str:
|
|
80
|
+
ret_val : str
|
|
81
|
+
|
|
82
|
+
ret_val = f"{self.Name}"
|
|
83
|
+
return ret_val
|
|
84
|
+
|
|
85
|
+
@staticmethod
|
|
86
|
+
def _parseDataSources(sources, logger:logging.Logger) -> Dict[str, MySQLSchema]:
|
|
87
|
+
ret_val : Dict[str, MySQLSchema]
|
|
88
|
+
if isinstance(sources, dict):
|
|
89
|
+
ret_val = {}
|
|
90
|
+
for key,val in sources.items():
|
|
91
|
+
ret_val[key] = MySQLSchema(name=key, all_elements=val)
|
|
92
|
+
else:
|
|
93
|
+
ret_val = {}
|
|
94
|
+
logger.warn(f"Config data sources was unexpected type {type(sources)}, defaulting to empty dict: {ret_val}.", logging.WARN)
|
|
95
|
+
return ret_val
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def _parseOGDPath(path, logger:logging.Logger) -> Path:
|
|
99
|
+
ret_val : Path
|
|
100
|
+
if isinstance(path, str):
|
|
101
|
+
ret_val = Path(path)
|
|
102
|
+
else:
|
|
103
|
+
ret_val = Path("./") / "opengamedata"
|
|
104
|
+
logger.warn(f"Data Source DB type was unexpected type {type(path)}, defaulting to path={ret_val}.", logging.WARN)
|
|
105
|
+
return ret_val
|
|
106
|
+
|
|
107
|
+
@staticmethod
|
|
108
|
+
def _parseGoogleID(google_id, logger:logging.Logger) -> str:
|
|
109
|
+
ret_val : str
|
|
110
|
+
if isinstance(google_id, str):
|
|
111
|
+
ret_val = google_id
|
|
112
|
+
else:
|
|
113
|
+
ret_val = str(google_id)
|
|
114
|
+
logger.warn(f"Google Client ID type was unexpected type {type(google_id)}, defaulting to google_client_id=str({ret_val}).", logging.WARN)
|
|
115
|
+
return ret_val
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def _parseDebugLevel(level, logger:logging.Logger) -> int:
|
|
119
|
+
ret_val : int
|
|
120
|
+
if isinstance(level, str):
|
|
121
|
+
match level.upper():
|
|
122
|
+
case "ERROR":
|
|
123
|
+
ret_val = logging.ERROR
|
|
124
|
+
case "WARNING" | "WARN":
|
|
125
|
+
ret_val = logging.WARN
|
|
126
|
+
case "INFO":
|
|
127
|
+
ret_val = logging.INFO
|
|
128
|
+
case "DEBUG":
|
|
129
|
+
ret_val = logging.DEBUG
|
|
130
|
+
case _:
|
|
131
|
+
ret_val = logging.INFO
|
|
132
|
+
logger.warn(f"Config debug level had unexpected value {level}, defaulting to logging.INFO.", logging.WARN)
|
|
133
|
+
else:
|
|
134
|
+
ret_val = logging.INFO
|
|
135
|
+
logger.warn(f"Config debug level was unexpected type {type(level)}, defaulting to logging.INFO.", logging.WARN)
|
|
136
|
+
return ret_val
|
|
137
|
+
|
|
138
|
+
@staticmethod
|
|
139
|
+
def _parseVersion(version, logger:logging.Logger) -> int:
|
|
140
|
+
ret_val : int
|
|
141
|
+
if isinstance(version, int):
|
|
142
|
+
ret_val = version
|
|
143
|
+
elif isinstance(version, str):
|
|
144
|
+
ret_val = int(version)
|
|
145
|
+
else:
|
|
146
|
+
ret_val = int(str(version))
|
|
147
|
+
logger.warn(f"Config version was unexpected type {type(version)}, defaulting to int(str(version))={ret_val}.", logging.WARN)
|
|
148
|
+
return ret_val
|
|
File without changes
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""
|
|
2
|
+
APIResponse
|
|
3
|
+
|
|
4
|
+
Contains class for representing a response from an OGD API,
|
|
5
|
+
as well as utility enums used by the APIResponse class.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# import standard libraries
|
|
9
|
+
import json
|
|
10
|
+
from enum import IntEnum
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
# import 3rd-party libraries
|
|
14
|
+
|
|
15
|
+
# import OGD libraries
|
|
16
|
+
import ogd.core.requests.RequestResult as RequestResult
|
|
17
|
+
|
|
18
|
+
# Import local files
|
|
19
|
+
|
|
20
|
+
class RESTType(IntEnum):
|
|
21
|
+
"""Simple enumerated type to track type of a REST request.
|
|
22
|
+
"""
|
|
23
|
+
GET = 1
|
|
24
|
+
POST = 2
|
|
25
|
+
PUT = 3
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
"""Stringify function for RESTTypes.
|
|
29
|
+
|
|
30
|
+
:return: Simple string version of the name of a RESTType
|
|
31
|
+
:rtype: _type_
|
|
32
|
+
"""
|
|
33
|
+
match self.value:
|
|
34
|
+
case RESTType.GET:
|
|
35
|
+
return "GET"
|
|
36
|
+
case RESTType.POST:
|
|
37
|
+
return "POST"
|
|
38
|
+
case RESTType.PUT:
|
|
39
|
+
return "PUT"
|
|
40
|
+
case _:
|
|
41
|
+
return "INVALID REST TYPE"
|
|
42
|
+
|
|
43
|
+
class ResponseStatus(IntEnum):
|
|
44
|
+
"""Simple enumerated type to track the status of an API request result.
|
|
45
|
+
"""
|
|
46
|
+
NONE = 1
|
|
47
|
+
SUCCESS = 200
|
|
48
|
+
ERR_REQ = 400
|
|
49
|
+
ERR_SRV = 500
|
|
50
|
+
|
|
51
|
+
def __str__(self):
|
|
52
|
+
"""Stringify function for ResponseStatus objects.
|
|
53
|
+
|
|
54
|
+
:return: Simple string version of the name of a ResponseStatus
|
|
55
|
+
:rtype: _type_
|
|
56
|
+
"""
|
|
57
|
+
match self.value:
|
|
58
|
+
case ResponseStatus.NONE:
|
|
59
|
+
return "NONE"
|
|
60
|
+
case ResponseStatus.SUCCESS:
|
|
61
|
+
return "SUCCESS"
|
|
62
|
+
case ResponseStatus.ERR_SRV:
|
|
63
|
+
return "SERVER ERROR"
|
|
64
|
+
case ResponseStatus.ERR_REQ:
|
|
65
|
+
return "REQUEST ERROR"
|
|
66
|
+
case _:
|
|
67
|
+
return "INVALID STATUS TYPE"
|
|
68
|
+
|
|
69
|
+
class APIResponse:
|
|
70
|
+
def __init__(self, req_type:RESTType, val:Any, msg:str, status:ResponseStatus):
|
|
71
|
+
self._type : RESTType = req_type
|
|
72
|
+
self._val : Dict[str, Any] = val
|
|
73
|
+
self._msg : str = msg
|
|
74
|
+
self._status : ResponseStatus = status
|
|
75
|
+
|
|
76
|
+
def __str__(self):
|
|
77
|
+
return f"{self.Type.name} request: {self.Status}\n{self.Message}\nValues: {self.Value}"
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def Default(req_type:RESTType):
|
|
81
|
+
return APIResponse(
|
|
82
|
+
req_type=req_type,
|
|
83
|
+
val=None,
|
|
84
|
+
msg="",
|
|
85
|
+
status=ResponseStatus.NONE
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def FromRequestResult(result:RequestResult.RequestResult, req_type:RESTType):
|
|
90
|
+
_status : ResponseStatus
|
|
91
|
+
if result.Status == RequestResult.ResultStatus.SUCCESS:
|
|
92
|
+
_status = ResponseStatus.SUCCESS
|
|
93
|
+
elif result.Status == RequestResult.ResultStatus.FAILURE:
|
|
94
|
+
_status = ResponseStatus.ERR_REQ
|
|
95
|
+
else:
|
|
96
|
+
_status = ResponseStatus.ERR_SRV
|
|
97
|
+
ret_val = APIResponse(req_type=req_type, val=None, msg=result.Message, status=_status)
|
|
98
|
+
return ret_val
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def Type(self) -> RESTType:
|
|
102
|
+
"""Property for the type of REST request
|
|
103
|
+
|
|
104
|
+
:return: A RESTType representing the type of REST request
|
|
105
|
+
:rtype: _type_
|
|
106
|
+
"""
|
|
107
|
+
return self._type
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def Value(self) -> Dict[str, Any]:
|
|
111
|
+
"""Property for the value of the request result.
|
|
112
|
+
|
|
113
|
+
:return: Some value, of any type, returned from the request.
|
|
114
|
+
:rtype: Any
|
|
115
|
+
"""
|
|
116
|
+
return self._val
|
|
117
|
+
@Value.setter
|
|
118
|
+
def Value(self, new_val:Dict[str, Any]):
|
|
119
|
+
self._val = new_val
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def Message(self) -> str:
|
|
124
|
+
"""Property for the message associated with a request result.
|
|
125
|
+
|
|
126
|
+
:return: A string message giving details on the result of the request.
|
|
127
|
+
:rtype: str
|
|
128
|
+
"""
|
|
129
|
+
return self._msg
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def Status(self) -> ResponseStatus:
|
|
133
|
+
"""Property for the status of the request.
|
|
134
|
+
|
|
135
|
+
:return: A ResponseStatus indicating whether request is/was successful, incomplete, failed, etc.
|
|
136
|
+
:rtype: ResponseStatus
|
|
137
|
+
"""
|
|
138
|
+
return self._status
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def AsDict(self):
|
|
142
|
+
return {
|
|
143
|
+
"type" : str(self._type),
|
|
144
|
+
"val" : json.dumps(self._val),
|
|
145
|
+
"msg" : self._msg,
|
|
146
|
+
"status" : str(self._status)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def AsJSON(self):
|
|
151
|
+
return json.dumps(self.AsDict)
|
|
152
|
+
|
|
153
|
+
def RequestErrored(self, msg:str):
|
|
154
|
+
self._status = ResponseStatus.ERR_REQ
|
|
155
|
+
self._msg = f"ERROR: {msg}"
|
|
156
|
+
|
|
157
|
+
def ServerErrored(self, msg:str):
|
|
158
|
+
self._status = ResponseStatus.ERR_SRV
|
|
159
|
+
self._msg = f"SERVER ERROR: {msg}"
|
|
160
|
+
|
|
161
|
+
def RequestSucceeded(self, msg:str, val:Any):
|
|
162
|
+
self._status = ResponseStatus.SUCCESS
|
|
163
|
+
self._msg = f"SUCCESS: {msg}"
|
|
164
|
+
self._val = val
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
APIUtils
|
|
3
|
+
|
|
4
|
+
Contains general utility functions for common tasks when setting up our flask/flask-restful API functions.
|
|
5
|
+
In particular, has functions to assist in parsing certain kinds of data, and for generating OGD-core objects.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# import standard libraries
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
from typing import Any, List, Optional
|
|
12
|
+
|
|
13
|
+
# import 3rd-party libraries
|
|
14
|
+
from flask import current_app
|
|
15
|
+
|
|
16
|
+
# import OGD libraries
|
|
17
|
+
from ogd.core.interfaces.DataInterface import DataInterface
|
|
18
|
+
from ogd.core.interfaces.MySQLInterface import MySQLInterface
|
|
19
|
+
from ogd.core.interfaces.BigQueryInterface import BigQueryInterface
|
|
20
|
+
from ogd.core.schemas.configs.ConfigSchema import ConfigSchema
|
|
21
|
+
from ogd.core.schemas.configs.GameSourceSchema import GameSourceSchema
|
|
22
|
+
|
|
23
|
+
# import local files
|
|
24
|
+
|
|
25
|
+
def parse_list(list_str:str) -> Optional[List[Any]]:
|
|
26
|
+
"""Simple utility to parse a string containing a bracketed list into a Python list.
|
|
27
|
+
Returns None if the list was empty
|
|
28
|
+
|
|
29
|
+
:param list_str: _description_
|
|
30
|
+
:type list_str: str
|
|
31
|
+
:return: A list parsed from the input string, or None if the string list was invalid or empty.
|
|
32
|
+
:rtype: Union[List[Any], None]
|
|
33
|
+
"""
|
|
34
|
+
ret_val : Optional[List[Any]] = None
|
|
35
|
+
try:
|
|
36
|
+
ret_val = json.loads(list_str)
|
|
37
|
+
except json.decoder.JSONDecodeError as e:
|
|
38
|
+
current_app.logger.warn(f"Could not parse '{list_str}' as a list, format was not valid!\nGot Error {e}")
|
|
39
|
+
else:
|
|
40
|
+
if ret_val is not None and len(ret_val) == 0:
|
|
41
|
+
# If we had empty list, just treat as null.
|
|
42
|
+
ret_val = None
|
|
43
|
+
return ret_val
|
|
44
|
+
|
|
45
|
+
def gen_interface(game_id, core_config:ConfigSchema) -> Optional[DataInterface]:
|
|
46
|
+
"""Utility to set up an Interface object for use by the API, given a game_id.
|
|
47
|
+
|
|
48
|
+
:param game_id: _description_
|
|
49
|
+
:type game_id: _type_
|
|
50
|
+
:return: _description_
|
|
51
|
+
:rtype: _type_
|
|
52
|
+
"""
|
|
53
|
+
ret_val = None
|
|
54
|
+
|
|
55
|
+
_game_source : GameSourceSchema = core_config.GameSourceMap.get(game_id, GameSourceSchema.EmptySchema())
|
|
56
|
+
|
|
57
|
+
if _game_source.Source is not None:
|
|
58
|
+
# set up interface and request
|
|
59
|
+
if _game_source.Source.Type.upper() == "MYSQL":
|
|
60
|
+
ret_val = MySQLInterface(game_id, config=_game_source, fail_fast=False)
|
|
61
|
+
current_app.logger.info(f"Using MySQLInterface for {game_id}")
|
|
62
|
+
elif _game_source.Source.Type.upper() == "BIGQUERY":
|
|
63
|
+
current_app.logger.info(f"Generating BigQueryInterface for {game_id}, from directory {os.getcwd()}...")
|
|
64
|
+
ret_val = BigQueryInterface(game_id=game_id, config=_game_source, fail_fast=False)
|
|
65
|
+
current_app.logger.info("Done")
|
|
66
|
+
else:
|
|
67
|
+
ret_val = MySQLInterface(game_id, config=_game_source, fail_fast=False)
|
|
68
|
+
current_app.logger.warning(f"Could not find a valid interface for {game_id}, defaulting to MySQL!")
|
|
69
|
+
return ret_val
|
|
70
|
+
|
|
71
|
+
# def gen_coding_interface(game_id) -> Optional[CodingInterface]:
|
|
72
|
+
# """Utility to set up an Interface object for use by the API, given a game_id.
|
|
73
|
+
|
|
74
|
+
# :param game_id: _description_
|
|
75
|
+
# :type game_id: _type_
|
|
76
|
+
# :return: _description_
|
|
77
|
+
# :rtype: _type_
|
|
78
|
+
# """
|
|
79
|
+
# ret_val = None
|
|
80
|
+
|
|
81
|
+
# _core_config = ConfigSchema(name="Core Config", all_elements=core_settings)
|
|
82
|
+
# _game_source : GameSourceSchema = _core_config.GameSourceMap.get(game_id, GameSourceSchema.EmptySchema())
|
|
83
|
+
|
|
84
|
+
# if _game_source.Source is not None:
|
|
85
|
+
# # set up interface and request
|
|
86
|
+
# if _game_source.Source.Type == "BigQuery":
|
|
87
|
+
# ret_val = BigQueryCodingInterface(game_id=game_id, config=_core_config)
|
|
88
|
+
# current_app.logger.info(f"Using BigQueryCodingInterface for {game_id}")
|
|
89
|
+
# else:
|
|
90
|
+
# ret_val = BigQueryCodingInterface(game_id=game_id, config=_core_config)
|
|
91
|
+
# current_app.logger.warning(f"Could not find a valid interface for {game_id}, defaulting to BigQuery!")
|
|
92
|
+
# return ret_val
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HelloAPI
|
|
3
|
+
|
|
4
|
+
Contains the HelloAPI class, which we register to all API apps as a way to test that the app is active.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# import libraries
|
|
8
|
+
|
|
9
|
+
# import 3rd-party libraries
|
|
10
|
+
from flask import Flask
|
|
11
|
+
from flask_restful import Resource, Api
|
|
12
|
+
|
|
13
|
+
# import OGD libraries
|
|
14
|
+
from ogd.apis.utils.APIResponse import APIResponse, RESTType, ResponseStatus
|
|
15
|
+
|
|
16
|
+
# import locals
|
|
17
|
+
|
|
18
|
+
class HelloAPI:
|
|
19
|
+
@staticmethod
|
|
20
|
+
def register(app:Flask):
|
|
21
|
+
api = Api(app)
|
|
22
|
+
api.add_resource(HelloAPI.Hello, '/hello')
|
|
23
|
+
api.add_resource(HelloAPI.ParamHello, '/p_hello/<name>')
|
|
24
|
+
|
|
25
|
+
class Hello(Resource):
|
|
26
|
+
def get(self):
|
|
27
|
+
ret_val = APIResponse(
|
|
28
|
+
req_type = RESTType.GET,
|
|
29
|
+
val = None,
|
|
30
|
+
msg = "Hello! You GETted successfully!",
|
|
31
|
+
status = ResponseStatus.SUCCESS)
|
|
32
|
+
return ret_val.AsDict
|
|
33
|
+
|
|
34
|
+
def post(self):
|
|
35
|
+
ret_val = APIResponse(
|
|
36
|
+
req_type = RESTType.POST,
|
|
37
|
+
val = None,
|
|
38
|
+
msg = "Hello! You POSTed successfully!",
|
|
39
|
+
status = ResponseStatus.SUCCESS)
|
|
40
|
+
return ret_val.AsDict
|
|
41
|
+
|
|
42
|
+
def put(self):
|
|
43
|
+
ret_val = APIResponse(
|
|
44
|
+
req_type = RESTType.PUT,
|
|
45
|
+
val = None,
|
|
46
|
+
msg = "Hello! You PUTted successfully!",
|
|
47
|
+
status = ResponseStatus.SUCCESS)
|
|
48
|
+
return ret_val.AsDict
|
|
49
|
+
|
|
50
|
+
class ParamHello(Resource):
|
|
51
|
+
def get(self, name):
|
|
52
|
+
ret_val = APIResponse(
|
|
53
|
+
req_type = RESTType.GET,
|
|
54
|
+
val = None,
|
|
55
|
+
msg = f"Hello {name}! You GETted successfully!",
|
|
56
|
+
status = ResponseStatus.SUCCESS)
|
|
57
|
+
return ret_val.AsDict
|
|
58
|
+
|
|
59
|
+
def post(self, name):
|
|
60
|
+
ret_val = APIResponse(
|
|
61
|
+
req_type = RESTType.POST,
|
|
62
|
+
val = None,
|
|
63
|
+
msg = f"Hello {name}! You POSTed successfully!",
|
|
64
|
+
status = ResponseStatus.SUCCESS)
|
|
65
|
+
return ret_val.AsDict
|
|
66
|
+
|
|
67
|
+
def put(self, name):
|
|
68
|
+
ret_val = APIResponse(
|
|
69
|
+
req_type = RESTType.PUT,
|
|
70
|
+
val = None,
|
|
71
|
+
msg = f"Hello {name}! You PUTted successfully!",
|
|
72
|
+
status = ResponseStatus.SUCCESS)
|
|
73
|
+
return ret_val.AsDict
|
|
File without changes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: opengamedata-api-utils
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Package of utilities for server-side scripts in OpenGameData.
|
|
5
|
+
Author: Ryan Wilkinson, Glenn Palmer, Daus Husaini
|
|
6
|
+
Author-email: Luke Swanson <superscription58@gmail.com>
|
|
7
|
+
Project-URL: Homepage, https://github.com/opengamedata/opengamedata-api-utils
|
|
8
|
+
Project-URL: Bug Tracker, https://github.com/opengamedata/opengamedata-api-utils/issues
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# opengamedata-server
|
|
17
|
+
|
|
18
|
+
Repository for server-side scripts in opengamedata. In particular, this is where we have code for the OGD APIs.
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
### Hello World of Flask
|
|
23
|
+
|
|
24
|
+
Steps to run:
|
|
25
|
+
1. Check out latest `opengamedata-server`.
|
|
26
|
+
2. Run `pip install -r requirements.txt` to ensure you've got flask.
|
|
27
|
+
3. Run `flask run`.
|
|
28
|
+
4. Open localhost:5000 or localhost:5000/hello to see some really basic text output from the Flask server.
|
|
29
|
+
|
|
30
|
+
If Flask doesn't run, it's possible you'd need to first export FLASK_APP as an environment variable, set to "wsgi" (so in Bash, export FLASK_APP=wsgi).
|
|
31
|
+
However, the script is named wsgi.py specifically because Flask is supposed to auto-detect it. So if this issue ever did come up, please ping Luke so he can look into it.
|
|
32
|
+
|
|
33
|
+
## APIs
|
|
34
|
+
|
|
35
|
+
Below is a listing of the current API calls available, in function format to indicate what the request parameters.
|
|
36
|
+
For each API, there is also an api path, with path parameters in angle bracket (<, >) format.
|
|
37
|
+
Lastly, at this point in time, the `<server_path>` is `https://fieldday-web.wcer.wisc.edu/opengamedata.wsgi`
|
|
38
|
+
|
|
39
|
+
### Classroom API
|
|
40
|
+
|
|
41
|
+
### Dashboard API
|
|
42
|
+
|
|
43
|
+
### Hello API
|
|
44
|
+
|
|
45
|
+
#### Verify the API is alive
|
|
46
|
+
|
|
47
|
+
`<server_path>/hello`
|
|
48
|
+
`GET()`
|
|
49
|
+
|
|
50
|
+
- returns no value, and a success message
|
|
51
|
+
|
|
52
|
+
`POST()`
|
|
53
|
+
|
|
54
|
+
- returns no value, and a success message
|
|
55
|
+
|
|
56
|
+
`PUT()`
|
|
57
|
+
|
|
58
|
+
- returns no value, and a success message
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
### Player API
|
|
62
|
+
|
|
63
|
+
### Generate and save players
|
|
64
|
+
|
|
65
|
+
`<server_path>/player`
|
|
66
|
+
`GET()`
|
|
67
|
+
|
|
68
|
+
- returns an unused, randomized player name, or a null value and error message
|
|
69
|
+
|
|
70
|
+
`PUT(str player_id, str name = None)`
|
|
71
|
+
|
|
72
|
+
- Returns no value, and either a success or error message
|
|
73
|
+
---
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/ogd/apis/__init__.py
|
|
5
|
+
src/ogd/apis/schemas/ServerConfigSchema.py
|
|
6
|
+
src/ogd/apis/schemas/__init__.py
|
|
7
|
+
src/ogd/apis/utils/APIResponse.py
|
|
8
|
+
src/ogd/apis/utils/APIUtils.py
|
|
9
|
+
src/ogd/apis/utils/HelloAPI.py
|
|
10
|
+
src/ogd/apis/utils/__init__.py
|
|
11
|
+
src/opengamedata_api_utils.egg-info/PKG-INFO
|
|
12
|
+
src/opengamedata_api_utils.egg-info/SOURCES.txt
|
|
13
|
+
src/opengamedata_api_utils.egg-info/dependency_links.txt
|
|
14
|
+
src/opengamedata_api_utils.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ogd
|