Skip to content

Commit ece4466

Browse files
authored
Merge pull request #295 from bgilbert/pathlib
Switch examples and `jekyll_fix` from `os.path` to `pathlib`
2 parents bbbc0eb + 012fbb1 commit ece4466

File tree

4 files changed

+83
-80
lines changed

4 files changed

+83
-80
lines changed

doc/jekyll_fix.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# openslide-python - Python bindings for the OpenSlide library
33
#
44
# Copyright (c) 2014 Carnegie Mellon University
5+
# Copyright (c) 2024 Benjamin Gilbert
56
#
67
# This library is free software; you can redistribute it and/or modify it
78
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -26,6 +27,7 @@
2627
from __future__ import annotations
2728

2829
import os
30+
from pathlib import Path
2931

3032
from sphinx.application import Sphinx
3133
from sphinx.util import logging
@@ -49,37 +51,33 @@ def remove_path_underscores(app: Sphinx, exception: Exception | None) -> None:
4951
logger = logging.getLogger(__name__)
5052
logger.info(bold('fixing pathnames... '), nonl=True)
5153
# Rewrite references in HTML/JS files
52-
for dirpath, _, filenames in os.walk(app.outdir):
54+
outdir = Path(app.outdir)
55+
for dirpath, _, filenames in os.walk(outdir):
5356
for filename in filenames:
54-
_, ext = os.path.splitext(filename)
55-
if ext in REWRITE_EXTENSIONS:
56-
path = os.path.join(dirpath, filename)
57-
with open(path, encoding='utf-8') as fh:
57+
path = Path(dirpath) / filename
58+
if path.suffix in REWRITE_EXTENSIONS:
59+
with path.open(encoding='utf-8') as fh:
5860
contents = fh.read()
5961
for old, new in DIRS.items():
6062
contents = contents.replace(old + '/', new + '/')
6163
for old, new in FILES.items():
6264
contents = contents.replace(old, new)
63-
with open(path, 'w', encoding='utf-8') as fh:
65+
with path.open('w', encoding='utf-8') as fh:
6466
fh.write(contents)
6567
# Move directory contents
6668
for old, new in DIRS.items():
67-
olddir = os.path.join(app.outdir, old)
68-
newdir = os.path.join(app.outdir, new)
69-
if not os.path.exists(newdir):
70-
os.mkdir(newdir)
71-
if os.path.isdir(olddir):
72-
for filename in os.listdir(olddir):
73-
oldfile = os.path.join(olddir, filename)
74-
newfile = os.path.join(newdir, filename)
75-
os.rename(oldfile, newfile)
76-
os.rmdir(olddir)
69+
olddir = outdir / old
70+
newdir = outdir / new
71+
newdir.mkdir(exist_ok=True)
72+
if olddir.is_dir():
73+
for oldfile in olddir.iterdir():
74+
oldfile.rename(newdir / oldfile.name)
75+
olddir.rmdir()
7776
# Move files
7877
for old, new in FILES.items():
79-
oldfile = os.path.join(app.outdir, old)
80-
newfile = os.path.join(app.outdir, new)
81-
if os.path.isfile(oldfile):
82-
os.rename(oldfile, newfile)
78+
oldfile = outdir / old
79+
if oldfile.is_file():
80+
oldfile.rename(outdir / new)
8381
logger.info('done')
8482

8583

examples/deepzoom/deepzoom_multiserver.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# deepzoom_multiserver - Example web application for viewing multiple slides
44
#
55
# Copyright (c) 2010-2015 Carnegie Mellon University
6-
# Copyright (c) 2021-2023 Benjamin Gilbert
6+
# Copyright (c) 2021-2024 Benjamin Gilbert
77
#
88
# This library is free software; you can redistribute it and/or modify it
99
# under the terms of version 2.1 of the GNU Lesser General Public License
@@ -27,6 +27,7 @@
2727
from collections.abc import Callable
2828
from io import BytesIO
2929
import os
30+
from pathlib import Path, PurePath
3031
from threading import Lock
3132
from typing import TYPE_CHECKING, Any, Literal
3233
import zlib
@@ -82,7 +83,7 @@
8283

8384

8485
class DeepZoomMultiServer(Flask):
85-
basedir: str
86+
basedir: Path
8687
cache: _SlideCache
8788

8889

@@ -94,7 +95,7 @@ class AnnotatedDeepZoomGenerator(DeepZoomGenerator):
9495

9596
def create_app(
9697
config: dict[str, Any] | None = None,
97-
config_file: str | None = None,
98+
config_file: Path | None = None,
9899
) -> Flask:
99100
# Create and configure app
100101
app = DeepZoomMultiServer(__name__)
@@ -116,7 +117,7 @@ def create_app(
116117
app.config.from_mapping(config)
117118

118119
# Set up cache
119-
app.basedir = os.path.abspath(app.config['SLIDE_DIR'])
120+
app.basedir = Path(app.config['SLIDE_DIR']).resolve(strict=True)
120121
config_map = {
121122
'DEEPZOOM_TILE_SIZE': 'tile_size',
122123
'DEEPZOOM_OVERLAP': 'overlap',
@@ -131,16 +132,18 @@ def create_app(
131132
)
132133

133134
# Helper functions
134-
def get_slide(path: str) -> AnnotatedDeepZoomGenerator:
135-
path = os.path.abspath(os.path.join(app.basedir, path))
136-
if not path.startswith(app.basedir + os.path.sep):
137-
# Directory traversal
135+
def get_slide(user_path: PurePath) -> AnnotatedDeepZoomGenerator:
136+
try:
137+
path = (app.basedir / user_path).resolve(strict=True)
138+
except OSError:
139+
# Does not exist
138140
abort(404)
139-
if not os.path.exists(path):
141+
if path.parts[: len(app.basedir.parts)] != app.basedir.parts:
142+
# Directory traversal
140143
abort(404)
141144
try:
142145
slide = app.cache.get(path)
143-
slide.filename = os.path.basename(path)
146+
slide.filename = path.name
144147
return slide
145148
except OpenSlideError:
146149
abort(404)
@@ -152,7 +155,7 @@ def index() -> str:
152155

153156
@app.route('/<path:path>')
154157
def slide(path: str) -> str:
155-
slide = get_slide(path)
158+
slide = get_slide(PurePath(path))
156159
slide_url = url_for('dzi', path=path)
157160
return render_template(
158161
'slide-fullpage.html',
@@ -163,15 +166,15 @@ def slide(path: str) -> str:
163166

164167
@app.route('/<path:path>.dzi')
165168
def dzi(path: str) -> Response:
166-
slide = get_slide(path)
169+
slide = get_slide(PurePath(path))
167170
format = app.config['DEEPZOOM_FORMAT']
168171
resp = make_response(slide.get_dzi(format))
169172
resp.mimetype = 'application/xml'
170173
return resp
171174

172175
@app.route('/<path:path>_files/<int:level>/<int:col>_<int:row>.<format>')
173176
def tile(path: str, level: int, col: int, row: int, format: str) -> Response:
174-
slide = get_slide(path)
177+
slide = get_slide(PurePath(path))
175178
format = format.lower()
176179
if format != 'jpeg' and format != 'png':
177180
# Not supported by Deep Zoom
@@ -208,7 +211,7 @@ def __init__(
208211
self.dz_opts = dz_opts
209212
self.color_mode = color_mode
210213
self._lock = Lock()
211-
self._cache: OrderedDict[str, AnnotatedDeepZoomGenerator] = OrderedDict()
214+
self._cache: OrderedDict[Path, AnnotatedDeepZoomGenerator] = OrderedDict()
212215
# Share a single tile cache among all slide handles, if supported
213216
try:
214217
self._tile_cache: OpenSlideCache | None = OpenSlideCache(
@@ -217,7 +220,7 @@ def __init__(
217220
except OpenSlideVersionError:
218221
self._tile_cache = None
219222

220-
def get(self, path: str) -> AnnotatedDeepZoomGenerator:
223+
def get(self, path: Path) -> AnnotatedDeepZoomGenerator:
221224
with self._lock:
222225
if path in self._cache:
223226
# Move to end of LRU
@@ -286,13 +289,14 @@ def xfrm(img: Image.Image) -> None:
286289

287290

288291
class _Directory:
289-
def __init__(self, basedir: str, relpath: str = ''):
290-
self.name = os.path.basename(relpath)
292+
_DEFAULT_RELPATH = PurePath('.')
293+
294+
def __init__(self, basedir: Path, relpath: PurePath = _DEFAULT_RELPATH):
295+
self.name = relpath.name
291296
self.children: list[_Directory | _SlideFile] = []
292-
for name in sorted(os.listdir(os.path.join(basedir, relpath))):
293-
cur_relpath = os.path.join(relpath, name)
294-
cur_path = os.path.join(basedir, cur_relpath)
295-
if os.path.isdir(cur_path):
297+
for cur_path in sorted((basedir / relpath).iterdir()):
298+
cur_relpath = relpath / cur_path.name
299+
if cur_path.is_dir():
296300
cur_dir = _Directory(basedir, cur_relpath)
297301
if cur_dir.children:
298302
self.children.append(cur_dir)
@@ -301,9 +305,9 @@ def __init__(self, basedir: str, relpath: str = ''):
301305

302306

303307
class _SlideFile:
304-
def __init__(self, relpath: str):
305-
self.name = os.path.basename(relpath)
306-
self.url_path = relpath
308+
def __init__(self, relpath: PurePath):
309+
self.name = relpath.name
310+
self.url_path = relpath.as_posix()
307311

308312

309313
if __name__ == '__main__':
@@ -336,7 +340,7 @@ def __init__(self, relpath: str):
336340
),
337341
)
338342
parser.add_argument(
339-
'-c', '--config', metavar='FILE', dest='config', help='config file'
343+
'-c', '--config', metavar='FILE', type=Path, dest='config', help='config file'
340344
)
341345
parser.add_argument(
342346
'-d',
@@ -396,6 +400,7 @@ def __init__(self, relpath: str):
396400
parser.add_argument(
397401
'SLIDE_DIR',
398402
metavar='SLIDE-DIRECTORY',
403+
type=Path,
399404
nargs='?',
400405
help='slide directory',
401406
)

examples/deepzoom/deepzoom_server.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from collections.abc import Callable
2727
from io import BytesIO
2828
import os
29+
from pathlib import Path
2930
import re
3031
from typing import TYPE_CHECKING, Any, Literal, Mapping
3132
from unicodedata import normalize
@@ -93,7 +94,7 @@ class DeepZoomServer(Flask):
9394

9495
def create_app(
9596
config: dict[str, Any] | None = None,
96-
config_file: str | None = None,
97+
config_file: Path | None = None,
9798
) -> Flask:
9899
# Create and configure app
99100
app = DeepZoomServer(__name__)
@@ -113,9 +114,9 @@ def create_app(
113114
app.config.from_mapping(config)
114115

115116
# Open slide
116-
slidefile = app.config['DEEPZOOM_SLIDE']
117-
if slidefile is None:
117+
if app.config['DEEPZOOM_SLIDE'] is None:
118118
raise ValueError('No slide file specified')
119+
slidefile = Path(app.config['DEEPZOOM_SLIDE'])
119120
config_map = {
120121
'DEEPZOOM_TILE_SIZE': 'tile_size',
121122
'DEEPZOOM_OVERLAP': 'overlap',
@@ -273,7 +274,7 @@ def xfrm(img: Image.Image) -> None:
273274
),
274275
)
275276
parser.add_argument(
276-
'-c', '--config', metavar='FILE', dest='config', help='config file'
277+
'-c', '--config', metavar='FILE', type=Path, dest='config', help='config file'
277278
)
278279
parser.add_argument(
279280
'-d',
@@ -333,6 +334,7 @@ def xfrm(img: Image.Image) -> None:
333334
parser.add_argument(
334335
'DEEPZOOM_SLIDE',
335336
metavar='SLIDE',
337+
type=Path,
336338
nargs='?',
337339
help='slide file',
338340
)

0 commit comments

Comments
 (0)