Skip to content

Commit 8ba9e6d

Browse files
committed
Converts code to python3
1 parent bc04afe commit 8ba9e6d

File tree

3 files changed

+58
-19
lines changed

3 files changed

+58
-19
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
*.pyc
1+
*.pyc
2+
.idea
3+
venv

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Dependencies
99

1010
The dependencies are:
1111

12-
1. *Python* 2.7 is known to work. (see below for Python 2.6)
12+
1. *Python* 3.6 is known to work. (see below for older versions of Python)
1313
1. *ExifRead* for EXIF metadata.
1414
1. *sh* the Python shell command utility.
1515
1. *bottlepy* the Python web micro-framework.
@@ -146,6 +146,10 @@ to configure access to the asset server:
146146
If these properties do not already exist, they can be added using the *Add Property*
147147
button.
148148

149+
Python 2.7 compatibility
150+
------------------------
151+
Web Asset server for Python 2.7 can be found here - [https://github.com/specify/web-asset-server](https://github.com/specify/web-asset-server)
152+
149153
Python 2.6 compatibility
150154
------------------------
151155

server.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
1-
from os import path, mkdir, remove
2-
from mimetypes import guess_type
3-
from sh import convert
4-
from glob import glob
5-
from urllib import pathname2url, quote
61
from collections import defaultdict, OrderedDict
72
from functools import wraps
8-
import exifread, json, hmac, time
3+
from glob import glob
4+
from mimetypes import guess_type
5+
from os import path, mkdir, remove
6+
from urllib.parse import quote
7+
from urllib.request import pathname2url
98

9+
import exifread
10+
import hmac
11+
import json
12+
import time
13+
from sh import convert
14+
15+
import settings
1016
from bottle import (
1117
Response, request, response, static_file, template, abort,
12-
HTTPError, HTTPResponse, route, hook)
18+
HTTPResponse, route)
1319

14-
import settings
1520

1621
def log(msg):
1722
if settings.DEBUG:
18-
print msg
23+
print(msg)
24+
1925

2026
def get_rel_path(coll, thumb_p):
2127
"""Return originals or thumbnails subdirectory of the main
@@ -33,6 +39,7 @@ def get_rel_path(coll, thumb_p):
3339

3440
return path.join(coll_dir, type_dir)
3541

42+
3643
def generate_token(timestamp, filename):
3744
"""Generate the auth token for the given filename and timestamp.
3845
This is for comparing to the client submited token.
@@ -41,16 +48,19 @@ def generate_token(timestamp, filename):
4148
mac = hmac.new(settings.KEY, timestamp + filename)
4249
return ':'.join((mac.hexdigest(), timestamp))
4350

51+
4452
class TokenException(Exception):
4553
"""Raised when an auth token is invalid for some reason."""
4654
pass
4755

56+
4857
def get_timestamp():
4958
"""Return an integer timestamp with one second resolution for
5059
the current moment.
5160
"""
5261
return int(time.time())
5362

63+
5464
def validate_token(token_in, filename):
5565
"""Validate the input token for given filename using the secret key
5666
in settings. Checks that the token is within the time tolerance and
@@ -77,6 +87,7 @@ def validate_token(token_in, filename):
7787
if token_in != generate_token(timestamp, filename):
7888
raise TokenException("Auth token is invalid.")
7989

90+
8091
def require_token(filename_param, always=False):
8192
"""Decorate a view function to require an auth token to be present for access.
8293
@@ -89,6 +100,7 @@ def require_token(filename_param, always=False):
89100
Automatically adds the X-Timestamp header to responses to help clients stay
90101
syncronized.
91102
"""
103+
92104
def decorator(func):
93105
@include_timestamp
94106
@wraps(func)
@@ -102,23 +114,30 @@ def wrapper(*args, **kwargs):
102114
response.status = 403
103115
return e
104116
return func(*args, **kwargs)
117+
105118
return wrapper
119+
106120
return decorator
107121

122+
108123
def include_timestamp(func):
109124
"""Decorate a view function to include the X-Timestamp header to help clients
110125
maintain time syncronization.
111126
"""
127+
112128
@wraps(func)
113129
def wrapper(*args, **kwargs):
114130
result = func(*args, **kwargs)
115131
(result if isinstance(result, Response) else response) \
116-
.set_header('X-Timestamp', str(get_timestamp()))
132+
.set_header('X-Timestamp', str(get_timestamp()))
117133
return result
134+
118135
return wrapper
119136

137+
120138
def allow_cross_origin(func):
121139
"""Decorate a view function to allow cross domain access."""
140+
122141
@wraps(func)
123142
def wrapper(*args, **kwargs):
124143
try:
@@ -128,10 +147,12 @@ def wrapper(*args, **kwargs):
128147
raise
129148

130149
(result if isinstance(result, Response) else response) \
131-
.set_header('Access-Control-Allow-Origin', '*')
150+
.set_header('Access-Control-Allow-Origin', '*')
132151
return result
152+
133153
return wrapper
134154

155+
135156
def resolve_file():
136157
"""Inspect the request object to determine the file being requested.
137158
If the request is for a thumbnail and it has not been generated, do
@@ -179,21 +200,23 @@ def resolve_file():
179200
input_spec = orig_path
180201
convert_args = ('-resize', "%dx%d>" % (scale, scale))
181202
if mimetype == 'application/pdf':
182-
input_spec += '[0]' # only thumbnail first page of PDF
203+
input_spec += '[0]' # only thumbnail first page of PDF
183204
convert_args += ('-background', 'white', '-flatten') # add white background to PDFs
184205

185206
log("Scaling thumbnail to %d" % scale)
186207
convert(input_spec, *(convert_args + (scaled_pathname,)))
187208

188209
return path.join(relpath, scaled_name)
189210

211+
190212
@route('/static/<path:path>')
191213
def static(path):
192214
"""Serve static files to the client. Primarily for Web Portal."""
193215
if not settings.ALLOW_STATIC_FILE_ACCESS:
194216
abort(404)
195217
return static_file(path, root=settings.BASE_DIR)
196218

219+
197220
@route('/getfileref')
198221
@allow_cross_origin
199222
def getfileref():
@@ -203,6 +226,8 @@ def getfileref():
203226
response.content_type = 'text/plain; charset=utf-8'
204227
return "http://%s:%d/static/%s" % (settings.HOST, settings.PORT,
205228
pathname2url(resolve_file()))
229+
230+
206231
@route('/fileget')
207232
@require_token('filename')
208233
def fileget():
@@ -214,12 +239,14 @@ def fileget():
214239
r.set_header('Content-Disposition', "inline; filename*=utf-8''%s" % download_name)
215240
return r
216241

242+
217243
@route('/fileupload', method='OPTIONS')
218244
@allow_cross_origin
219245
def fileupload_options():
220246
response.content_type = "text/plain; charset=utf-8"
221247
return ''
222248

249+
223250
@route('/fileupload', method='POST')
224251
@allow_cross_origin
225252
@require_token('store')
@@ -238,12 +265,13 @@ def fileupload():
238265
if not path.exists(basepath):
239266
mkdir(basepath)
240267

241-
upload = request.files.values()[0]
268+
upload = list(request.files.values())[0]
242269
upload.save(pathname, overwrite=True)
243270

244271
response.content_type = 'text/plain; charset=utf-8'
245272
return 'Ok.'
246273

274+
247275
@route('/filedelete', method='POST')
248276
@require_token('filename')
249277
def filedelete():
@@ -271,6 +299,7 @@ def filedelete():
271299
response.content_type = 'text/plain; charset=utf-8'
272300
return 'Ok.'
273301

302+
274303
@route('/getmetadata')
275304
@require_token('filename')
276305
def getmetadata():
@@ -297,7 +326,7 @@ def getmetadata():
297326
abort(404, 'DateTime not found in EXIF')
298327

299328
data = defaultdict(dict)
300-
for key, value in tags.items():
329+
for key, value in list(tags.items()):
301330
parts = key.split()
302331
if len(parts) < 2: continue
303332
try:
@@ -308,28 +337,32 @@ def getmetadata():
308337
data[parts[0]][parts[1]] = str(v)
309338

310339
response.content_type = 'application/json'
311-
data = [OrderedDict( (('Name', key), ('Fields', value)) )
312-
for key,value in data.items()]
340+
data = [OrderedDict((('Name', key), ('Fields', value)))
341+
for key, value in list(data.items())]
313342

314343
return json.dumps(data, indent=4)
315344

345+
316346
@route('/testkey')
317347
@require_token('random', always=True)
318348
def testkey():
319349
"""If access to this resource succeeds, clients can conclude
320350
that they have a valid access key.
321351
"""
322-
response.content_type ='text/plain; charset=utf-8'
352+
response.content_type = 'text/plain; charset=utf-8'
323353
return 'Ok.'
324354

355+
325356
@route('/web_asset_store.xml')
326357
@include_timestamp
327358
def web_asset_store():
328359
"""Serve an XML description of the URLs available here."""
329360
response.content_type = 'text/xml; charset=utf-8'
330361
return template('web_asset_store.xml', host="%s:%d" % (settings.HOST, settings.PORT))
331362

363+
332364
if __name__ == '__main__':
333365
from bottle import run
366+
334367
run(host='0.0.0.0', port=settings.PORT, server=settings.SERVER,
335368
debug=settings.DEBUG, reloader=settings.DEBUG)

0 commit comments

Comments
 (0)