otter-service-stdalone 0.1.16__tar.gz → 0.1.18__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.
Files changed (26) hide show
  1. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/PKG-INFO +1 -1
  2. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/setup.cfg +1 -1
  3. otter_service_stdalone-0.1.18/src/otter_service_stdalone/__init__.py +1 -0
  4. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone/access_sops_keys.py +6 -6
  5. otter_service_stdalone-0.1.18/src/otter_service_stdalone/app.py +235 -0
  6. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone/fs_logging.py +1 -1
  7. otter_service_stdalone-0.1.18/src/otter_service_stdalone/grade_notebooks.py +103 -0
  8. otter_service_stdalone-0.1.18/src/otter_service_stdalone/secrets/gh_key.dev.yaml +17 -0
  9. otter_service_stdalone-0.1.18/src/otter_service_stdalone/secrets/gh_key.local.yaml +16 -0
  10. otter_service_stdalone-0.1.18/src/otter_service_stdalone/secrets/gh_key.prod.yaml +16 -0
  11. otter_service_stdalone-0.1.18/src/otter_service_stdalone/secrets/gh_key.staging.yaml +16 -0
  12. otter_service_stdalone-0.1.18/src/otter_service_stdalone/static_templates/403.html +24 -0
  13. otter_service_stdalone-0.1.18/src/otter_service_stdalone/static_templates/500.html +24 -0
  14. otter_service_stdalone-0.1.18/src/otter_service_stdalone/user_auth.py +120 -0
  15. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone.egg-info/PKG-INFO +1 -1
  16. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone.egg-info/SOURCES.txt +8 -0
  17. otter_service_stdalone-0.1.16/src/otter_service_stdalone/__init__.py +0 -1
  18. otter_service_stdalone-0.1.16/src/otter_service_stdalone/app.py +0 -296
  19. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/README.md +0 -0
  20. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/pyproject.toml +0 -0
  21. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone/index.html +0 -0
  22. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone/upload_handle.py +0 -0
  23. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone.egg-info/dependency_links.txt +0 -0
  24. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone.egg-info/entry_points.txt +0 -0
  25. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/src/otter_service_stdalone.egg-info/top_level.txt +0 -0
  26. {otter_service_stdalone-0.1.16 → otter_service_stdalone-0.1.18}/tests/test_upload_handle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: otter_service_stdalone
3
- Version: 0.1.16
3
+ Version: 0.1.18
4
4
  Summary: Grading Service for Instructors using Otter Grader
5
5
  Home-page: https://github.com/sean-morris/otter-service-stdalone
6
6
  Author: Sean Morris
@@ -23,7 +23,7 @@ python_requires = >=3.8
23
23
  where = src
24
24
 
25
25
  [options.package_data]
26
- otter_service_stdalone = index.html
26
+ otter_service_stdalone = index.html, secrets/*.yaml, static_templates/*.html
27
27
 
28
28
  [options.entry_points]
29
29
  console_scripts =
@@ -0,0 +1 @@
1
+ __version__ = "0.1.18"
@@ -14,10 +14,10 @@ def get(course_key, key_name, sops_path=None, secrets_file=None) -> str:
14
14
  :return: the secret
15
15
  """
16
16
  try:
17
- secret = get_via_env(course_key, key_name)
18
- if secret is None:
19
- secret = get_via_sops(course_key, key_name, sops_path=sops_path, secrets_file=secrets_file)
20
- return secret
17
+ sec = get_via_env(course_key, key_name)
18
+ if sec is None:
19
+ sec = get_via_sops(course_key, key_name, sops_path=sops_path, secrets_file=secrets_file)
20
+ return sec
21
21
  except Exception as ex:
22
22
  raise Exception(f"Key not decrypted: {key_name}; please configure: Error: {ex}") from ex
23
23
 
@@ -38,8 +38,8 @@ def get_via_sops(course_key, key, sops_path=None, secrets_file=None):
38
38
 
39
39
  secrets_file = secrets_file
40
40
 
41
- sops_output = subprocess.check_output([sops_path, "-d", secrets_file], stderr=subprocess.STDOUT)
42
- dct = yaml.safe_load(sops_output)
41
+ sops_ot = subprocess.check_output([sops_path, "-d", secrets_file], stderr=subprocess.STDOUT)
42
+ dct = yaml.safe_load(sops_ot)
43
43
  if course_key is None:
44
44
  return dct[key]
45
45
  return dct[course_key][key]
@@ -0,0 +1,235 @@
1
+ import tornado
2
+ import tornado.ioloop
3
+ import tornado.web
4
+ import tornado.auth
5
+ import os
6
+ import uuid
7
+ from otter_service_stdalone import fs_logging as log
8
+ from otter_service_stdalone import user_auth as u_auth
9
+ from otter_service_stdalone import grade_notebooks
10
+ from zipfile import ZipFile, ZIP_DEFLATED
11
+
12
+
13
+ __UPLOADS__ = "/tmp/uploads"
14
+ log_debug = f'{os.environ.get("ENVIRONMENT")}-debug'
15
+ log_error = f'{os.environ.get("ENVIRONMENT")}-logs'
16
+
17
+ state = str(uuid.uuid4()) # used to protect against cross-site request forgery attacks.
18
+
19
+
20
+ class LoginHandler(tornado.web.RequestHandler):
21
+ """Initiaties login auth by authorizing access to github auth api
22
+
23
+ Args:
24
+ tornado (tornado.web.RequestHandler): The request handler
25
+ """
26
+ async def get(self):
27
+ await u_auth.handle_authorization(self, state)
28
+
29
+
30
+ class BaseHandler(tornado.web.RequestHandler):
31
+ """This is the super class for the handlers. get_current_user is called by
32
+ any handler that decorated with @tornado.web.authenticated
33
+
34
+ Args:
35
+ tornado (tornado.web.RequestHandler): The request handler
36
+ """
37
+ def get_current_user(self):
38
+ return self.get_secure_cookie("user")
39
+
40
+ def write_error(self, status_code, **kwargs):
41
+ log.write_logs("Http Error", f"{status_code} Error", "", "info", log_error)
42
+ if status_code == 403:
43
+ self.set_status(403)
44
+ self.render("static_templates/403.html")
45
+ else:
46
+ self.set_status(500)
47
+ self.render("static_templates/500.html")
48
+
49
+
50
+ class GitHubOAuthHandler(BaseHandler):
51
+ """Handles GitHubOAuth
52
+
53
+ Args:
54
+ tornado (tornado.web.RequestHandler): The request handler
55
+ """
56
+ async def get(self):
57
+ code = self.get_argument('code', False)
58
+ arg_state = self.get_argument('state', False)
59
+ access_token = await u_auth.get_acess_token(arg_state, state, code)
60
+ if access_token:
61
+ user = await u_auth.get_github_username(access_token)
62
+ is_org_member = await u_auth.handle_is_org_member(access_token, user)
63
+ if is_org_member:
64
+ self.set_secure_cookie("user", user, expires_days=7)
65
+ self.redirect("/")
66
+ else:
67
+ raise tornado.web.HTTPError(403)
68
+ else:
69
+ raise tornado.web.HTTPError(500)
70
+
71
+
72
+ class MainHandler(BaseHandler):
73
+ """This is the initial landing page for application
74
+
75
+ Args:
76
+ BaseHandler (BaseHandler): super class
77
+ """
78
+ @tornado.web.authenticated
79
+ async def get(self):
80
+ self.render("index.html", message=None)
81
+
82
+
83
+ class Download(BaseHandler):
84
+ """The class handling a request to download results
85
+
86
+ Args:
87
+ tornado (tornado.web.RequestHandler): The download request handler
88
+ """
89
+ @tornado.web.authenticated
90
+ def get(self):
91
+ # this just redirects to login and displays main page
92
+ self.render("index.html", message=None)
93
+
94
+ @tornado.web.authenticated
95
+ async def post(self):
96
+ """the post method that accepts the code used to locate the results
97
+ the user wants to download
98
+ """
99
+ download_code = self.get_argument('download')
100
+ directory = f"{__UPLOADS__}/{download_code}"
101
+ if download_code == "":
102
+ m = "Download: Code Not Given!"
103
+ log.write_logs(download_code, m, f"{download_code}", "debug", log_debug)
104
+ msg = "Please enter the download code to see your result."
105
+ self.render("index.html", download_message=msg)
106
+ elif not os.path.exists(f"{directory}"):
107
+ m = "Download: Directory for Code Not existing"
108
+ log.write_logs(download_code, m, f"{download_code}", "debug", log_debug)
109
+ msg = "The download code appears to not be correct or expired "
110
+ msg += f"- results are deleted regularly: {download_code}."
111
+ msg += "Please check the code or upload your notebooks "
112
+ msg += "and autograder.zip for grading again."
113
+ self.render("index.html", download_message=msg)
114
+ elif not os.path.exists(f"{directory}/grading-logs.txt"):
115
+ m = "Download: Results Not Ready"
116
+ log.write_logs(download_code, m, f"{download_code}", "debug", log_debug)
117
+ msg = "The results of your download are not ready yet. "
118
+ msg += "Please check back."
119
+ self.render("index.html", download_message=msg, dcode=download_code)
120
+ else:
121
+ if not os.path.isfile(f"{directory}/final_grades.csv"):
122
+ m = "Download: final_grades.csv does not exist"
123
+ t = "Problem grading notebooks see stack trace"
124
+ log.write_logs(download_code, m, t, "debug", log_debug)
125
+ with open(f"{directory}/final_grades.csv", "a") as f:
126
+ m = "There was a problem grading your notebooks. Please see grading-logs.txt"
127
+ f.write(m)
128
+ f.close()
129
+ m = "Download Success: Creating results.zip"
130
+ log.write_logs(download_code, m, "", "debug", log_debug)
131
+ with ZipFile(f"{directory}/results.zip", 'w') as zipF:
132
+ for file in ["final_grades.csv", "grading-logs.txt"]:
133
+ if os.path.isfile(f"{directory}/{file}"):
134
+ zipF.write(f"{directory}/{file}", file, compress_type=ZIP_DEFLATED)
135
+
136
+ self.set_header('Content-Type', 'application/octet-stream')
137
+ self.set_header("Content-Description", "File Transfer")
138
+ m = f"attachment; filename=results-{download_code}.zip"
139
+ self.set_header('Content-Disposition', m)
140
+ with open(f"{directory}/results.zip", 'rb') as f:
141
+ try:
142
+ while True:
143
+ data = f.read(4096)
144
+ if not data:
145
+ break
146
+ self.write(data)
147
+ self.finish()
148
+ except Exception as exc:
149
+ self.write(exc)
150
+
151
+
152
+ class Upload(BaseHandler):
153
+ """This is the upload handler for users to upload autograder.zip and notebooks
154
+
155
+ Args:
156
+ tornado (tornado.web.RequestHandler): The upload request handler
157
+ """
158
+ @tornado.web.authenticated
159
+ def get(self):
160
+ # this just redirects to login and displays main page
161
+ self.render("index.html", message=None)
162
+
163
+ @tornado.web.authenticated
164
+ async def post(self):
165
+ """this handles the post request and asynchronously launches the grader
166
+ """
167
+ g = grade_notebooks.GradeNotebooks()
168
+ files = self.request.files
169
+ results_path = str(uuid.uuid4())
170
+ autograder = self.request.files['autograder'][0] if "autograder" in files else None
171
+ notebooks = self.request.files['notebooks'][0] if "notebooks" in files else None
172
+ log.write_logs(results_path, "Step 1: Upload accepted", "", "debug", log_debug)
173
+ if autograder is not None and notebooks is not None:
174
+ notebooks_fname = notebooks['filename']
175
+ notebooks_extn = os.path.splitext(notebooks_fname)[1]
176
+ notebooks_name = results_path + notebooks_extn
177
+ autograder_fname = autograder['filename']
178
+ autograder_extn = os.path.splitext(autograder_fname)[1]
179
+ autograder_name = str(uuid.uuid4()) + autograder_extn
180
+ if not os.path.exists(__UPLOADS__):
181
+ os.mkdir(__UPLOADS__)
182
+ auto_p = f"{__UPLOADS__}/{autograder_name}"
183
+ notebooks_path = f"{__UPLOADS__}/{notebooks_name}"
184
+ m = "Step 2a: Uploaded File Names Determined"
185
+ log.write_logs(results_path, m, f"notebooks path: {notebooks_path}", "debug", log_debug)
186
+ fh = open(auto_p, 'wb')
187
+ fh.write(autograder['body'])
188
+
189
+ fh = open(notebooks_path, 'wb')
190
+ fh.write(notebooks['body'])
191
+ m = "Step 3: Uploaded Files Written to Disk"
192
+ log.write_logs(results_path, m, f"Results Code: {results_path}", "debug", log_debug)
193
+ m = "Please save this code. You can retrieve your files by submitting this code "
194
+ m += f"in the \"Results\" section to the right: {results_path}"
195
+ self.render("index.html", message=m)
196
+ try:
197
+ await g.grade(auto_p, notebooks_path, results_path)
198
+ except Exception as e:
199
+ log.write_logs(results_path, "Grading Problem", str(e), "error", log_error)
200
+ else:
201
+ m = "Step 2b: Uploaded Files not given"
202
+ log.write_logs(results_path, m, "", "debug", log_debug)
203
+ m = "It looks like you did not set the notebooks or autograder.zip or both!"
204
+ self.render("index.html", message=m)
205
+
206
+
207
+ settings = {
208
+ "cookie_secret": str(uuid.uuid4()),
209
+ "xsrf_cookies": True,
210
+ "login_url": "/login"
211
+ }
212
+
213
+ application = tornado.web.Application([
214
+ (r"/", MainHandler),
215
+ (r"/login", LoginHandler),
216
+ (r"/upload", Upload),
217
+ (r"/download", Download),
218
+ (r"/oauth_callback", GitHubOAuthHandler),
219
+ ], **settings, debug=False)
220
+
221
+
222
+ def main():
223
+ """the web servers entry point
224
+ """
225
+ try:
226
+ application.listen(80)
227
+ log.write_logs("Server Start", "Starting Server", "", "info", log_debug)
228
+ tornado.ioloop.IOLoop.instance().start()
229
+ except Exception as e:
230
+ m = "Server Starting error"
231
+ log.write_logs("Server Start Error", m, str(e), "error", log_debug)
232
+
233
+
234
+ if __name__ == "__main__":
235
+ main()
@@ -50,7 +50,7 @@ def write_logs(id, msg, trace, type, collection):
50
50
  try:
51
51
  db = firestore.client()
52
52
  # this redirects FireStore to local emulator when local testing!
53
- if os.getenv("ENVIRONMENT") == "otter-stdalone-docker-local-test":
53
+ if "local" in os.getenv("ENVIRONMENT"):
54
54
  channel = grpc.insecure_channel("host.docker.internal:8080")
55
55
  transport = firestore_grpc_transport.FirestoreGrpcTransport(channel=channel)
56
56
  db._firestore_api_internal = firestore_client.FirestoreClient(transport=transport)
@@ -0,0 +1,103 @@
1
+ import asyncio
2
+ import async_timeout
3
+ from otter_service_stdalone import fs_logging as log, upload_handle as uh
4
+ import os
5
+
6
+ log_debug = f'{os.environ.get("ENVIRONMENT")}-debug'
7
+ log_count = f'{os.environ.get("ENVIRONMENT")}-count'
8
+ log_error = f'{os.environ.get("ENVIRONMENT")}-logs'
9
+
10
+
11
+ class GradeNotebooks():
12
+ """The class contains the async grade method for executing
13
+ otter grader as well as a function for logging the number of
14
+ notebooks to be graded
15
+ """
16
+
17
+ def count_ipynb_files(self, directory, extension):
18
+ """this count the files for logging purposes"""
19
+ count = 0
20
+ for filename in os.listdir(directory):
21
+ if filename.endswith(extension):
22
+ count += 1
23
+ return count
24
+
25
+ async def grade(self, p, notebooks_path, results_id):
26
+ """Calls otter grade asynchronously and writes the various log files
27
+ and results of grading generating by otter-grader
28
+
29
+ Args:
30
+ p (str): the path to autograder.zip -- the solutions
31
+ notebooks_path (str): the path to the folder of notebooks to be graded
32
+ results_id (str): used for identifying logs
33
+
34
+ Raises:
35
+ Exception: Timeout Exception is raised if async takes longer than 20 min
36
+
37
+ Returns:
38
+ boolean: True is the process completes; otherwise an Exception is thrown
39
+ """
40
+ try:
41
+ notebook_folder = uh.handle_upload(notebooks_path, results_id)
42
+ notebook_count = self.count_ipynb_files(notebook_folder, ".ipynb")
43
+ log.write_logs(results_id, f"{notebook_count}",
44
+ "",
45
+ "info",
46
+ f'{os.environ.get("ENVIRONMENT")}-count')
47
+ log.write_logs(results_id, "Step 5: Notebook Folder configured for grader",
48
+ f"Notebook Folder: {notebook_folder}",
49
+ "debug",
50
+ log_debug)
51
+ command = [
52
+ 'otter', 'grade',
53
+ '-a', p,
54
+ '-p', notebook_folder,
55
+ "--ext", "ipynb",
56
+ "--containers", "10",
57
+ "-o", notebook_folder,
58
+ "-v"
59
+ ]
60
+ log.write_logs(results_id, f"Step 6: Grading Start: {notebook_folder}",
61
+ " ".join(command),
62
+ "debug",
63
+ log_debug)
64
+ process = await asyncio.create_subprocess_exec(
65
+ *command,
66
+ stdin=asyncio.subprocess.PIPE,
67
+ stdout=asyncio.subprocess.PIPE,
68
+ stderr=asyncio.subprocess.PIPE
69
+ )
70
+
71
+ # this is waiting for communication back from the process
72
+ # some images are quite big and take some time to build the first
73
+ # time through - like 20 min for otter-grader
74
+ async with async_timeout.timeout(2000):
75
+ stdout, stderr = await process.communicate()
76
+
77
+ with open(f"{notebook_folder}/grading-output.txt", "w") as f:
78
+ for line in stdout.decode().splitlines():
79
+ f.write(line + "\n")
80
+ log.write_logs(results_id, "Step 7: Grading: Finished: Write: grading-output.txt",
81
+ f"{notebook_folder}/grading-output.txt",
82
+ "debug",
83
+ log_debug)
84
+ with open(f"{notebook_folder}/grading-logs.txt", "w") as f:
85
+ for line in stderr.decode().splitlines():
86
+ f.write(line + "\n")
87
+ log.write_logs(results_id, "Step 8: Grading: Finished: Write grading-logs.txt",
88
+ f"{notebook_folder}/grading-logs.txt",
89
+ "debug",
90
+ log_debug)
91
+ log.write_logs(results_id, f"Step 9: Grading: Finished: {notebook_folder}",
92
+ " ".join(command),
93
+ "debug",
94
+ log_debug)
95
+ log.write_logs(results_id, f"Grading: Finished: {notebook_folder}",
96
+ " ".join(command),
97
+ "info",
98
+ log_error)
99
+ return True
100
+ except asyncio.TimeoutError:
101
+ raise Exception(f'Grading timed out for {notebook_folder}')
102
+ except Exception as e:
103
+ raise e
@@ -0,0 +1,17 @@
1
+ github_access_id: ENC[AES256_GCM,data:L97KjBroMsmVUYBOwIhGirB2nns=,iv:ZcBj74anvs43HziVBeQQRwl7hoEtX36+2wEQbFPNS0c=,tag:ptO+Tsx37xk1pWAGjctuhQ==,type:str]
2
+ github_access_secret: ENC[AES256_GCM,data:5DuzOLRI8HwW5KhskZQW+HzwPiIHBlYcBCMpfAt34dZg9lOB+ivrcw==,iv:Y4H6MrJT1dnO1HsOy6XYDQfUERXCssu+7/FP5SEEnCw=,tag:UYp6KrHWG4XrxY8LroSO6Q==,type:str]
3
+ sops:
4
+ kms: []
5
+ gcp_kms:
6
+ - resource_id: projects/ucb-datahub-2018/locations/global/keyRings/datahub/cryptoKeys/sops
7
+ created_at: "2024-04-03T18:29:45Z"
8
+ enc: CiUA67O9AArlsf5j4XCvPmf4qs3txe7+SxbVuJggNgW3GLh2DdACEkkAYHATHigxI73bJa6VfxZDxj5KLXsnz0ql72q7r7dzgSCwwjtFRrqizyloq2JvIoJskpjBcZBIe4VVVXHXsg8TUX/iMEB2G0Wm
9
+ azure_kv: []
10
+ hc_vault: []
11
+ age: []
12
+ lastmodified: "2024-04-03T18:29:46Z"
13
+ mac: ENC[AES256_GCM,data:QWZetePfWsqQQtCKc6O++f1falvJujzYMhxqRQFPmoUmQrFHBZ06v4XPPUciZGcrSgkhuyQwgHYXXSap1rDba2/D2i8jy8n8jC+hop60HLsveY53+ahYu3Fs91iQh3UdYg5ecTE0I/6prCdqVNL8nshYSBILYVNVG4KJAI7+G4s=,iv:4Beqcu1nYExqiv58Cs/YlCFGS1UV/MkAC5SWdmHr9Jw=,tag:h5PXnYsDtTFBFqkfGpPaJw==,type:str]
14
+ pgp: []
15
+ unencrypted_suffix: _unencrypted
16
+ version: 3.7.1
17
+
@@ -0,0 +1,16 @@
1
+ github_access_id: ENC[AES256_GCM,data:MULsaNCCrl+bF/HvLO00HKfCw54=,iv:Wi4txCUYoM+Zi3CHTSjqSuGAwUToZdggDOVuaZndHLA=,tag:CtKnanwlrdXLnPs74ZsqOQ==,type:str]
2
+ github_access_secret: ENC[AES256_GCM,data:h/cKH4nRjylg8ELxXsTvRuEuuB0P87R6SLqrbEBkQrjjgT+Bv/SVhQ==,iv:WmFvG6nijf9u0NE3ykJDaVxLdhbnDeiXbnexlYJDClE=,tag:HSO2CzLpCiqJJ9UdLn3+aw==,type:str]
3
+ sops:
4
+ kms: []
5
+ gcp_kms:
6
+ - resource_id: projects/ucb-datahub-2018/locations/global/keyRings/datahub/cryptoKeys/sops
7
+ created_at: "2024-03-26T22:42:25Z"
8
+ enc: CiUA67O9AORb6b4pqLYE7IAfXT1a6ZHA9TycUT/TmUecInMxiq8aEkkAYHATHnnEuC/W1e4eckSfEz+yQe5Eu7CrX7X8f8DTBIFLLyu6tFgSKjr3VxitXNmBhOY5rQ53e+M/QbgjeJ2LexKjuWCFck+A
9
+ azure_kv: []
10
+ hc_vault: []
11
+ age: []
12
+ lastmodified: "2024-03-26T22:42:26Z"
13
+ mac: ENC[AES256_GCM,data:n0QV+da9B/zogfbFCjY94Sr+uaTZBUjyjIvqOh7nIf/OCTjLwN6dsJlF6QCLlWIChTS+hfPn+8gEmJpn7gTrJIYfzO77ByP2hIxu+CAjLGHJ4V3/QrY3odPPzbiRv6KXpVpCt/JWXGcoimKMeFStwtaUDDWqh/xDbb7UwciXFxE=,iv:GAML0oOeBpI2SUW3NkNNJbWlhGBP4AyjUlZVG+qnds0=,tag:+1xj3mMmL33pBSWT6NyAjw==,type:str]
14
+ pgp: []
15
+ unencrypted_suffix: _unencrypted
16
+ version: 3.7.1
@@ -0,0 +1,16 @@
1
+ github_access_id: ENC[AES256_GCM,data:c8f28uHdSyPTkYqGeYQLTVilBfM=,iv:xF06JH1aACwzifY6dLpGyNC/Gp4UfUrRHhHEKRq86RQ=,tag:crN2FEg5rcam9XlWO26Bag==,type:str]
2
+ github_access_secret: ENC[AES256_GCM,data:paO5S9N9cn4/fiyG6P16550hL/r7dz8EHRMJLTaPdZgBtrHnaMq6HQ==,iv:qPAEDBcRJYyq0tPj7vPDPd+x1zhEhaTZm7TTOW3JNyU=,tag:9lKfrBt1Fnaq9T5J8jy4Hw==,type:str]
3
+ sops:
4
+ kms: []
5
+ gcp_kms:
6
+ - resource_id: projects/ucb-datahub-2018/locations/global/keyRings/datahub/cryptoKeys/sops
7
+ created_at: "2024-04-03T18:24:30Z"
8
+ enc: CiUA67O9AKNwFJ3SWLlDu7J9GHFv6BLZeu15atOKMwR5Z1pXtaPEEkkAYHATHmUfzxyp7nC6ly2ICTtW6rd7b9qG4w2N2n4rlC+0/eT0MjTQIDB4WBa65YGfp+8dYQXS6S6sBTK+ozbX6nXtcinddCcr
9
+ azure_kv: []
10
+ hc_vault: []
11
+ age: []
12
+ lastmodified: "2024-04-03T18:24:31Z"
13
+ mac: ENC[AES256_GCM,data:kCdn15/6xjxE3YYJMNJJaLmP7gswC2gEPop+ptkyq+TPu1ufG6kGaCaDbXScr6w7Qe/RXnzdeOdTp/nF7nSRL9YRH6acR2RXcWuV6ngWFUM+YJXgiM6uDB1Ps+yn2OdzEa1v8LZo0gGRxraHdScqTxJ0bL7koFQBKiJf7CWdhD8=,iv:VoFzGQLkufxBFZVtGMxK9x2jF2jIpVPvjdWB8Z3Edo4=,tag:KCix8V1a7V9vCbt4A7kxtA==,type:str]
14
+ pgp: []
15
+ unencrypted_suffix: _unencrypted
16
+ version: 3.7.1
@@ -0,0 +1,16 @@
1
+ github_access_id: ENC[AES256_GCM,data:3R9AMcMccaaF+1z3zuu2aehmJHI=,iv:mnhhWTTXsbAJN0FdoFzpc8WrImYLSM1chk3lqwxFSkw=,tag:RSBoplL8Ve7MjTW6Zz7WMQ==,type:str]
2
+ github_access_secret: ENC[AES256_GCM,data:+ebdQ0Jxy/bRe5plcuZZIrDoULu02jE+vj2HxoERYDG462YRONtiww==,iv:fHLqi4D1ixCdoOK5NASkkVLdX7kExnh8nOIOPaksnN0=,tag:9Jl2lJajSzO4UzJ/T+A8sw==,type:str]
3
+ sops:
4
+ kms: []
5
+ gcp_kms:
6
+ - resource_id: projects/ucb-datahub-2018/locations/global/keyRings/datahub/cryptoKeys/sops
7
+ created_at: "2024-04-03T18:27:16Z"
8
+ enc: CiUA67O9AKF7k4zHdbhRYNzT1cKTzkS1by8G/1516HbJQBlOsnf2EkkAYHATHos+I2G4tn7GIRJhjrsXLeN++ZUUyICCn3VB1Wzcm2udHOfce4GGGc0XcQc18Nvn1Zr/ToroSv9qI8DarocbEHZX+H/l
9
+ azure_kv: []
10
+ hc_vault: []
11
+ age: []
12
+ lastmodified: "2024-04-03T18:27:17Z"
13
+ mac: ENC[AES256_GCM,data:Q/A3QkCxo1GdcSB/gtAyB3DFaXKrcAtpzXpe6GJYD8jD1v5N7rAW/DFo459r5jfAM+9W4Ks3cwTheBJgJnqxfHRuWp6FcP6GefNuScGsiRequp/FQwJcTMnAgLOGCsOGbSfzzsNVEm7j0VpkTkgavr/rFrqAlVC1MFT++bJsXTw=,iv:P86phogwEJL0ZMkBWH+qJWS8MyHbI1/0iM399cyuLxQ=,tag:MdO0PHXoe893Mk0GTfms+g==,type:str]
14
+ pgp: []
15
+ unencrypted_suffix: _unencrypted
16
+ version: 3.7.1
@@ -0,0 +1,24 @@
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
4
+ <title>403 Forbidden</title>
5
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
6
+ <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
7
+ </head>
8
+ <body>
9
+ <section class="container">
10
+ <div class="hero is-info welcome is-small">
11
+ <div class="hero-body">
12
+ <div class="container">
13
+ <h1 class="title">403 Forbidden</h1>
14
+ <p class="subtitle colored is-3">
15
+ You do not have permission to access this resource or your authentication needs to be renewed.
16
+ Please <a style='color:#005b96' href="/login">login</a> to try again or
17
+ email sean.smorris@berkeley.edu for help.
18
+ </p>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </section>
23
+ </body>
24
+ </html>
@@ -0,0 +1,24 @@
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
4
+ <title>Access Problem</title>
5
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
6
+ <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
7
+ </head>
8
+ <body>
9
+ <section class="container">
10
+ <div class="hero is-info welcome is-small">
11
+ <div class="hero-body">
12
+ <div class="container">
13
+ <h1 class="title">500 Forbidden - Access Problem</h1>
14
+ <p class="subtitle colored is-3">
15
+ We are unable to establish access for you.
16
+ Please email: sean.smorris@berkeley.edu or try
17
+ <a style='color:#005b96' href="/login">logging</a> in again.
18
+ </p>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </section>
23
+ </body>
24
+ </html>
@@ -0,0 +1,120 @@
1
+ import json
2
+ import os
3
+ from otter_service_stdalone import fs_logging as log, access_sops_keys
4
+ import requests
5
+ import tornado
6
+ import urllib.parse
7
+
8
+
9
+ log_coll = f'{os.environ.get("ENVIRONMENT")}-debug'
10
+ environment_name = os.environ.get("ENVIRONMENT").split("-")[-1]
11
+ gh_key_path = os.path.join(os.path.dirname(__file__), f"secrets/gh_key.{environment_name}.yaml")
12
+ github_id = access_sops_keys.get(None, "github_access_id", secrets_file=gh_key_path)
13
+ github_secret = access_sops_keys.get(None, "github_access_secret", secrets_file=gh_key_path)
14
+
15
+
16
+ async def handle_authorization(form, state):
17
+ """authorizes via oauth app token access github auth api
18
+
19
+ Parameters:
20
+ - form (tornado.web.RequestHandler): The request handler used to re-direct for
21
+ authorization. The redirect url is configured at the github endpoint for the app
22
+ - state (int): random uuid4 number generated to ensure communication between endpoints is
23
+ not compromised
24
+ """
25
+ log.write_logs("Auth Workflow", "UserAuth: Get: Authorizing", "", "info", log_coll)
26
+ q_params = f"client_id={github_id}&state={state}&scope=read:org"
27
+ form.redirect(f'https://github.com/login/oauth/authorize?{q_params}')
28
+
29
+
30
+ async def get_acess_token(arg_state, state, code):
31
+ """requests and returns the access token or None
32
+
33
+ Parameters:
34
+ - arg_state (int): the state argument being passed on the url string; this will be compared
35
+ to the state value generated in this file to ensure they are the same!
36
+ - state (int): random uuid4 number generated to ensure communication between endpoints is
37
+ not compromised
38
+ - code (str): the code that is returned from the authorization request
39
+
40
+ Returns:
41
+ - str: the access token used for subsequent calls to the api; or None
42
+ """
43
+ http_client = tornado.httpclient.AsyncHTTPClient()
44
+ params = {
45
+ 'client_id': github_id,
46
+ 'client_secret': github_secret,
47
+ 'code': code,
48
+ 'redirect_uri': f"{os.environ.get('GRADER_DNS')}/oauth_callback"
49
+ }
50
+ m = "UserAuth: GitHubOAuthHandler: Getting Access Token"
51
+ log.write_logs("Auth Workflow", m, "", "info", log_coll)
52
+ response = await http_client.fetch(
53
+ 'https://github.com/login/oauth/access_token',
54
+ method='POST',
55
+ headers={'Accept': 'application/json'},
56
+ body=urllib.parse.urlencode(params)
57
+ )
58
+ resp = json.loads(response.body.decode())
59
+ access_token = resp['access_token']
60
+ if arg_state != state:
61
+ access_token = None
62
+ m = "UserAuth: GitHubOAuthHandler: Cross-Site Forgery possible - aborting"
63
+ log.write_logs("Auth Workflow", m, "", "info", log_coll)
64
+ else:
65
+ m = "UserAuth: GitHubOAuthHandler: Access Token Granted"
66
+ log.write_logs("Auth Workflow", m, "", "info", log_coll)
67
+ return access_token
68
+
69
+
70
+ async def handle_is_org_member(access_token, user):
71
+ """the final authorization is to make sure the member has access to the application by being
72
+ a part of the correct organizaton
73
+
74
+ Parameters:
75
+ - access_token (str): The OAuth access token.
76
+ - user (str): the authenticated GitHub user name
77
+
78
+ Returns:
79
+ - boolean: True user is in the GH org, False otherwise
80
+ """
81
+ log.write_logs("Auth Workflow", "UserAuth: Get: Check Membership", "", "info", log_coll)
82
+ if user:
83
+ org_name = os.environ.get("AUTH_ORG")
84
+ url = f'https://api.github.com/orgs/{org_name}/members/{user}'
85
+ headers = {
86
+ 'Authorization': f'token {access_token}',
87
+ 'Accept': 'application/vnd.github.v3+json',
88
+ }
89
+ response = requests.get(url, headers=headers)
90
+ return response.status_code == 204
91
+ else:
92
+ return False
93
+
94
+
95
+ async def get_github_username(access_token):
96
+ """
97
+ Retrieve the GitHub username of the authenticated user.
98
+
99
+ Parameters:
100
+ - access_token (str): The OAuth access token.
101
+
102
+ Returns:
103
+ - str: The username of the authenticated user, or None if not found.
104
+ """
105
+ url = 'https://api.github.com/user'
106
+ headers = {
107
+ 'Authorization': f'token {access_token}',
108
+ 'Accept': 'application/vnd.github.v3+json',
109
+ }
110
+
111
+ response = requests.get(url, headers=headers)
112
+ if response.status_code == 200:
113
+ m = "UserAuth: Get: UserName - Success"
114
+ log.write_logs("Auth Workflow", m, "", "info", log_coll)
115
+ user_info = response.json()
116
+ return user_info.get('login')
117
+ else:
118
+ m = f"UserAuth: Get: UserName - Fail:{response.status_code}"
119
+ log.write_logs("Auth Workflow", m, "", "info", log_coll)
120
+ return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: otter_service_stdalone
3
- Version: 0.1.16
3
+ Version: 0.1.18
4
4
  Summary: Grading Service for Instructors using Otter Grader
5
5
  Home-page: https://github.com/sean-morris/otter-service-stdalone
6
6
  Author: Sean Morris
@@ -5,11 +5,19 @@ src/otter_service_stdalone/__init__.py
5
5
  src/otter_service_stdalone/access_sops_keys.py
6
6
  src/otter_service_stdalone/app.py
7
7
  src/otter_service_stdalone/fs_logging.py
8
+ src/otter_service_stdalone/grade_notebooks.py
8
9
  src/otter_service_stdalone/index.html
9
10
  src/otter_service_stdalone/upload_handle.py
11
+ src/otter_service_stdalone/user_auth.py
10
12
  src/otter_service_stdalone.egg-info/PKG-INFO
11
13
  src/otter_service_stdalone.egg-info/SOURCES.txt
12
14
  src/otter_service_stdalone.egg-info/dependency_links.txt
13
15
  src/otter_service_stdalone.egg-info/entry_points.txt
14
16
  src/otter_service_stdalone.egg-info/top_level.txt
17
+ src/otter_service_stdalone/secrets/gh_key.dev.yaml
18
+ src/otter_service_stdalone/secrets/gh_key.local.yaml
19
+ src/otter_service_stdalone/secrets/gh_key.prod.yaml
20
+ src/otter_service_stdalone/secrets/gh_key.staging.yaml
21
+ src/otter_service_stdalone/static_templates/403.html
22
+ src/otter_service_stdalone/static_templates/500.html
15
23
  tests/test_upload_handle.py
@@ -1 +0,0 @@
1
- __version__ = "0.1.16"
@@ -1,296 +0,0 @@
1
- import tornado
2
- import tornado.ioloop
3
- import tornado.web
4
- import tornado.auth
5
- import os
6
- import uuid
7
- from otter_service_stdalone import fs_logging as log, upload_handle as uh
8
- from zipfile import ZipFile, ZIP_DEFLATED
9
- import asyncio
10
- import async_timeout
11
- import urllib.parse
12
- from otter_service_stdalone import access_sops_keys
13
- import json
14
-
15
- __UPLOADS__ = "/tmp/uploads"
16
-
17
-
18
- class GradeNotebooks():
19
- """The class contains the async grade method for executing
20
- otter grader
21
- """
22
- async def grade(self, p, notebooks_path, results_id):
23
- """Calls otter grade asynchronously and writes the various log files
24
- and results of grading generating by otter-grader
25
-
26
- Args:
27
- p (str): the path to autograder.zip -- the solutions
28
- notebooks_path (str): the path to the folder of notebooks to be graded
29
- results_id (str): used for identifying logs
30
-
31
- Raises:
32
- Exception: Timeout Exception is raised if async takes longer than 20 min
33
-
34
- Returns:
35
- boolean: True is the process completes; otherwise an Exception is thrown
36
- """
37
- try:
38
- notebook_folder = uh.handle_upload(notebooks_path, results_id)
39
- log.write_logs(results_id, "Step 5: Notebook Folder configured for grader",
40
- f"Notebook Folder: {notebook_folder}",
41
- "debug",
42
- f'{os.environ.get("ENVIRONMENT")}-debug')
43
- command = [
44
- 'otter', 'grade',
45
- '-a', p,
46
- '-p', notebook_folder,
47
- "--ext", "ipynb",
48
- "--containers", "10",
49
- "-o", notebook_folder,
50
- "-v"
51
- ]
52
- log.write_logs(results_id, f"Step 6: Grading Start: {notebook_folder}",
53
- " ".join(command),
54
- "debug",
55
- f'{os.environ.get("ENVIRONMENT")}-debug')
56
- process = await asyncio.create_subprocess_exec(
57
- *command,
58
- stdin=asyncio.subprocess.PIPE,
59
- stdout=asyncio.subprocess.PIPE,
60
- stderr=asyncio.subprocess.PIPE
61
- )
62
-
63
- # this is waiting for communication back from the process
64
- # some images are quite big and take some time to build the first
65
- # time through - like 20 min for otter-grader
66
- async with async_timeout.timeout(2000):
67
- stdout, stderr = await process.communicate()
68
-
69
- with open(f"{notebook_folder}/grading-output.txt", "w") as f:
70
- for line in stdout.decode().splitlines():
71
- f.write(line + "\n")
72
- log.write_logs(results_id, "Step 7: Grading: Finished: Write: grading-output.txt",
73
- f"{notebook_folder}/grading-output.txt",
74
- "debug",
75
- f'{os.environ.get("ENVIRONMENT")}-debug')
76
- with open(f"{notebook_folder}/grading-logs.txt", "w") as f:
77
- for line in stderr.decode().splitlines():
78
- f.write(line + "\n")
79
- log.write_logs(results_id, "Step 8: Grading: Finished: Write grading-logs.txt",
80
- f"{notebook_folder}/grading-logs.txt",
81
- "debug",
82
- f'{os.environ.get("ENVIRONMENT")}-debug')
83
- log.write_logs(results_id, f"Step 9: Grading: Finished: {notebook_folder}",
84
- " ".join(command),
85
- "debug",
86
- f'{os.environ.get("ENVIRONMENT")}-debug')
87
- log.write_logs(results_id, f"Grading: Finished: {notebook_folder}",
88
- " ".join(command),
89
- "info",
90
- f'{os.environ.get("ENVIRONMENT")}-logs')
91
- return True
92
- except asyncio.TimeoutError:
93
- raise Exception(f'Grading timed out for {notebook_folder}')
94
- except Exception as e:
95
- raise e
96
-
97
-
98
- class Userform(tornado.web.RequestHandler):
99
- """This is the initial landing page for application
100
-
101
- Args:
102
- tornado (tornado.web.RequestHandler): The request handler
103
- """
104
- async def get(self):
105
- # User will be redirected here by GitHub after authorization
106
- code = self.get_argument('code', False)
107
- if code:
108
- access_token = await self.get_authenticated_user(code)
109
- self.write(f'Your GitHub access token is: {access_token}')
110
- else:
111
- self.render("index.html", message=None)
112
-
113
- async def get_authenticated_user(self, code):
114
- secrets_file = os.path.join(os.path.dirname(__file__), "secrets/gh_key.yaml")
115
- github_id = access_sops_keys.get(None, "github_access_id", secrets_file=secrets_file)
116
- github_secret = access_sops_keys.get(None, "github_access_secret", secrets_file=secrets_file)
117
-
118
- http_client = tornado.httpclient.AsyncHTTPClient()
119
- params = {
120
- 'client_id': github_id,
121
- 'client_secret': github_secret,
122
- 'code': code,
123
- 'redirect_uri': "https://grader.datahub.berkeley.edu/oauth_callback"
124
- }
125
- response = await http_client.fetch(
126
- 'https://github.com/login/oauth/access_token',
127
- method='POST',
128
- headers={'Accept': 'application/json'},
129
- body=urllib.parse.urlencode(params)
130
- )
131
- return json.loads(response.body.decode())['access_token']
132
-
133
-
134
- class Download(tornado.web.RequestHandler):
135
- """The class handling a request to download results
136
-
137
- Args:
138
- tornado (tornado.web.RequestHandler): The download request handler
139
- """
140
- async def post(self):
141
- """the post method that accepts the code used to locate the results
142
- the user wants to download
143
- """
144
- code = self.get_argument('download')
145
- directory = f"{__UPLOADS__}/{code}"
146
- if code == "":
147
- log.write_logs(code, "Download: Code Not Given!",
148
- f"{code}",
149
- "debug",
150
- f'{os.environ.get("ENVIRONMENT")}-debug')
151
- msg = "Please enter the download code to see your result."
152
- self.render("index.html", download_message=msg)
153
- elif not os.path.exists(f"{directory}"):
154
- log.write_logs(code, "Download: Directory for Code Not existing",
155
- f"{code}",
156
- "debug",
157
- f'{os.environ.get("ENVIRONMENT")}-debug')
158
- msg = "The download code appears to not be correct or expired "
159
- msg += f"- results are deleted regularly: {code}."
160
- msg += "Please check the code or upload your notebooks "
161
- msg += "and autograder.zip for grading again."
162
- self.render("index.html", download_message=msg)
163
- elif not os.path.exists(f"{directory}/grading-logs.txt"):
164
- log.write_logs(code, "Download: Results Not Ready",
165
- f"{code}",
166
- "debug",
167
- f'{os.environ.get("ENVIRONMENT")}-debug')
168
- msg = "The results of your download are not ready yet. "
169
- msg += "Please check back."
170
- self.render("index.html", download_message=msg, dcode=code)
171
- else:
172
- if not os.path.isfile(f"{directory}/final_grades.csv"):
173
- log.write_logs(code, "Download: final_grades.csv does not exist",
174
- "Problem grading notebooks see stack trace",
175
- "debug",
176
- f'{os.environ.get("ENVIRONMENT")}-debug')
177
- with open(f"{directory}/final_grades.csv", "a") as f:
178
- m = "There was a problem grading your notebooks. Please see grading-logs.txt"
179
- f.write(m)
180
- f.close()
181
-
182
- log.write_logs(code, "Download Success: Creating results.zip",
183
- "",
184
- "debug",
185
- f'{os.environ.get("ENVIRONMENT")}-debug')
186
- with ZipFile(f"{directory}/results.zip", 'w') as zipF:
187
- for file in ["final_grades.csv", "grading-logs.txt"]:
188
- if os.path.isfile(f"{directory}/{file}"):
189
- zipF.write(f"{directory}/{file}", file, compress_type=ZIP_DEFLATED)
190
-
191
- self.set_header('Content-Type', 'application/octet-stream')
192
- self.set_header("Content-Description", "File Transfer")
193
- self.set_header('Content-Disposition', f"attachment; filename=results-{code}.zip")
194
- with open(f"{directory}/results.zip", 'rb') as f:
195
- try:
196
- while True:
197
- data = f.read(4096)
198
- if not data:
199
- break
200
- self.write(data)
201
- self.finish()
202
- except Exception as exc:
203
- self.write(exc)
204
-
205
-
206
- class Upload(tornado.web.RequestHandler):
207
- """This is the upload handler for users to upload autograder.zip and notebooks
208
-
209
- Args:
210
- tornado (tornado.web.RequestHandler): The upload request handler
211
- """
212
- async def post(self):
213
- """this handles the post request and asynchronously launches the grader
214
- """
215
- g = GradeNotebooks()
216
- files = self.request.files
217
- results_path = str(uuid.uuid4())
218
- autograder = self.request.files['autograder'][0] if "autograder" in files else None
219
- notebooks = self.request.files['notebooks'][0] if "notebooks" in files else None
220
- log.write_logs(results_path, "Step 1: Upload accepted",
221
- "",
222
- "debug",
223
- f'{os.environ.get("ENVIRONMENT")}-debug')
224
- if autograder is not None and notebooks is not None:
225
- notebooks_fname = notebooks['filename']
226
- notebooks_extn = os.path.splitext(notebooks_fname)[1]
227
- notebooks_name = results_path + notebooks_extn
228
- autograder_fname = autograder['filename']
229
- autograder_extn = os.path.splitext(autograder_fname)[1]
230
- autograder_name = str(uuid.uuid4()) + autograder_extn
231
- if not os.path.exists(__UPLOADS__):
232
- os.mkdir(__UPLOADS__)
233
- auto_p = f"{__UPLOADS__}/{autograder_name}"
234
- notebooks_path = f"{__UPLOADS__}/{notebooks_name}"
235
- log.write_logs(results_path, "Step 2a: Uploaded File Names Determined",
236
- f"notebooks path: {notebooks_path}",
237
- "debug",
238
- f'{os.environ.get("ENVIRONMENT")}-debug')
239
- fh = open(auto_p, 'wb')
240
- fh.write(autograder['body'])
241
-
242
- fh = open(notebooks_path, 'wb')
243
- fh.write(notebooks['body'])
244
- log.write_logs(results_path, "Step 3: Uploaded Files Written to Disk",
245
- f"Results Code: {results_path}",
246
- "debug",
247
- f'{os.environ.get("ENVIRONMENT")}-debug')
248
- m = "Please save this code. You can retrieve your files by submitting this code "
249
- m += f"in the \"Results\" section to the right: {results_path}"
250
- self.render("index.html", message=m)
251
- try:
252
- await g.grade(auto_p, notebooks_path, results_path)
253
- except Exception as e:
254
- log.write_logs(results_path, "Grading Problem",
255
- str(e),
256
- "error",
257
- f'{os.environ.get("ENVIRONMENT")}-logs')
258
- else:
259
- log.write_logs(results_path, "Step 2b: Uploaded Files not given",
260
- "",
261
- "debug",
262
- f'{os.environ.get("ENVIRONMENT")}-debug')
263
- m = "It looks like you did not set the notebooks or autograder.zip or both!"
264
- self.render("index.html", message=m)
265
-
266
-
267
- settings = {
268
- "cookie_secret": str(uuid.uuid4()),
269
- "xsrf_cookies": True
270
- }
271
-
272
- application = tornado.web.Application([
273
- (r"/", Userform),
274
- (r"/upload", Upload),
275
- (r"/download", Download),
276
- (r"/auth/github", Userform),
277
- ], **settings, debug=True)
278
-
279
-
280
- def main():
281
- """the web servers entry point
282
- """
283
- try:
284
- application.listen(80)
285
- msg = f'{os.environ.get("ENVIRONMENT")}-debug'
286
- log.write_logs("Server Start", "Starting Server", "", "info", msg)
287
- tornado.ioloop.IOLoop.instance().start()
288
- except Exception as e:
289
- log.write_logs("Server Start Error", "Server Starting error",
290
- str(e),
291
- "error",
292
- f'{os.environ.get("ENVIRONMENT")}-debug')
293
-
294
-
295
- if __name__ == "__main__":
296
- main()