Skip to content

Commit 6321765

Browse files
committed
Type annotations on plain/assets
1 parent 1099a2c commit 6321765

File tree

6 files changed

+66
-42
lines changed

6 files changed

+66
-42
lines changed

plain-admin/plain/admin/views/types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ def __init__(
66
alt: str = "",
77
width: int | None = None,
88
height: int | None = None,
9-
) -> None:
9+
):
1010
self.src = src
1111
self.alt = alt
1212
self.width = width

plain/plain/assets/compile.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
from __future__ import annotations
2+
13
import gzip
24
import os
35
import shutil
6+
from collections.abc import Iterator
7+
from pathlib import Path
48

59
from plain.runtime import PLAIN_TEMP_PATH
610

7-
from .finders import iter_assets
11+
from .finders import Asset, iter_assets
812
from .fingerprints import AssetsFingerprintsManifest, get_file_fingerprint
913

1014
SKIP_COMPRESS_EXTENSIONS = (
@@ -40,7 +44,7 @@
4044
)
4145

4246

43-
def get_compiled_path():
47+
def get_compiled_path() -> Path:
4448
"""
4549
Get the path at runtime to the compiled assets directory.
4650
@@ -49,7 +53,9 @@ def get_compiled_path():
4953
return PLAIN_TEMP_PATH / "assets" / "compiled"
5054

5155

52-
def compile_assets(*, target_dir, keep_original, fingerprint, compress):
56+
def compile_assets(
57+
*, target_dir: str, keep_original: bool, fingerprint: bool, compress: bool
58+
) -> Iterator[tuple[str, str, list[str]]]:
5359
"""
5460
Compile all assets to the target directory and save a JSON manifest
5561
mapping the original filenames to the compiled filenames.
@@ -73,17 +79,24 @@ def compile_assets(*, target_dir, keep_original, fingerprint, compress):
7379
manifest.save()
7480

7581

76-
def compile_asset(*, asset, target_dir, keep_original, fingerprint, compress):
82+
def compile_asset(
83+
*,
84+
asset: Asset,
85+
target_dir: str,
86+
keep_original: bool,
87+
fingerprint: bool,
88+
compress: bool,
89+
) -> tuple[str, list[str]]:
7790
"""
7891
Compile an asset to multiple output paths.
7992
"""
80-
compiled_paths = []
93+
compiled_paths: list[str] = []
8194

8295
# The expected destination for the original asset
8396
target_path = os.path.join(target_dir, asset.url_path)
8497

8598
# Keep track of where the final, resolved asset ends up
86-
resolved_url_path = asset.url_path
99+
resolved_url_path: str = asset.url_path
87100

88101
# Make sure all the expected directories exist
89102
os.makedirs(os.path.dirname(target_path), exist_ok=True)
@@ -106,7 +119,7 @@ def compile_asset(*, asset, target_dir, keep_original, fingerprint, compress):
106119
shutil.copy(asset.absolute_path, fingerprinted_path)
107120
compiled_paths.append(fingerprinted_path)
108121

109-
resolved_url_path = os.path.relpath(fingerprinted_path, target_dir)
122+
resolved_url_path = str(os.path.relpath(fingerprinted_path, target_dir))
110123

111124
if compress and extension.lower() not in SKIP_COMPRESS_EXTENSIONS:
112125
for path in compiled_paths.copy():

plain/plain/assets/finders.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
from __future__ import annotations
2+
13
import os
4+
from collections.abc import Iterator
25

36
from plain.packages import packages_registry
47
from plain.runtime import APP_PATH
@@ -8,21 +11,22 @@
811
SKIP_ASSETS = (".DS_Store", ".gitignore")
912

1013

11-
def iter_assets():
14+
class Asset:
15+
def __init__(self, *, url_path: str, absolute_path: str):
16+
self.url_path = url_path
17+
self.absolute_path = absolute_path
18+
19+
def __str__(self) -> str:
20+
return self.url_path
21+
22+
23+
def iter_assets() -> Iterator[Asset]:
1224
"""
1325
Iterate all valid asset files found in the installed
1426
packages and the app itself.
1527
"""
1628

17-
class Asset:
18-
def __init__(self, *, url_path, absolute_path):
19-
self.url_path = url_path
20-
self.absolute_path = absolute_path
21-
22-
def __str__(self):
23-
return self.url_path
24-
25-
def _iter_assets_dir(path):
29+
def _iter_assets_dir(path: str) -> Iterator[tuple[str, str]]:
2630
for root, _, files in os.walk(path):
2731
for f in files:
2832
if f in SKIP_ASSETS:
@@ -36,7 +40,7 @@ def _iter_assets_dir(path):
3640
yield Asset(url_path=url_path, absolute_path=abs_path)
3741

3842

39-
def iter_asset_dirs():
43+
def iter_asset_dirs() -> Iterator[str]:
4044
"""
4145
Iterate all directories containing assets, from installed
4246
packages and from app/assets.

plain/plain/assets/fingerprints.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ class AssetsFingerprintsManifest(dict):
1515
def __init__(self):
1616
self.path = PLAIN_TEMP_PATH / "assets" / "fingerprints.json"
1717

18-
def load(self):
18+
def load(self) -> None:
1919
if self.path.exists():
2020
with open(self.path) as f:
2121
self.update(json.load(f))
2222

23-
def save(self):
23+
def save(self) -> None:
2424
with open(self.path, "w") as f:
2525
json.dump(self, f, indent=2)
2626

2727

2828
@cache
29-
def _get_manifest():
29+
def _get_manifest() -> AssetsFingerprintsManifest:
3030
"""
3131
A cached function for loading the asset fingerprints manifest,
3232
so we don't have to keep loading it from disk over and over.
@@ -36,16 +36,17 @@ def _get_manifest():
3636
return manifest
3737

3838

39-
def get_fingerprinted_url_path(url_path):
39+
def get_fingerprinted_url_path(url_path: str) -> str | None:
4040
"""
4141
Get the final fingerprinted path for an asset URL path.
4242
"""
4343
manifest = _get_manifest()
4444
if url_path in manifest:
4545
return manifest[url_path]
46+
return None
4647

4748

48-
def get_file_fingerprint(file_path):
49+
def get_file_fingerprint(file_path: str) -> str:
4950
"""
5051
Get the fingerprint hash for a file.
5152
"""

plain/plain/assets/urls.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class AssetsRouter(Router):
1818
]
1919

2020

21-
def get_asset_url(url_path):
21+
def get_asset_url(url_path: str) -> str:
2222
"""
2323
Get the full URL to a given asset path.
2424
"""

plain/plain/assets/views.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import functools
24
import mimetypes
35
import os
@@ -28,14 +30,14 @@ class AssetView(View):
2830
This class could be subclassed to further tweak the responses or behavior.
2931
"""
3032

31-
def __init__(self, asset_path=None):
33+
def __init__(self, asset_path: str | None = None):
3234
# Allow a path to be passed in AssetView.as_view(path="...")
3335
self.asset_path = asset_path
3436

35-
def get_url_path(self):
37+
def get_url_path(self) -> str:
3638
return self.asset_path or self.url_kwargs["path"]
3739

38-
def get(self):
40+
def get(self) -> Response | FileResponse:
3941
url_path = self.get_url_path()
4042

4143
# Make a trailing slash work, but we don't expect it
@@ -71,7 +73,7 @@ def get(self):
7173
response.headers = self.update_headers(response.headers, absolute_path)
7274
return response
7375

74-
def get_asset_path(self, path):
76+
def get_asset_path(self, path: str) -> str:
7577
"""Get the path to the compiled asset"""
7678
compiled_path = os.path.abspath(get_compiled_path())
7779
asset_path = os.path.join(compiled_path, path)
@@ -82,13 +84,14 @@ def get_asset_path(self, path):
8284

8385
return asset_path
8486

85-
def get_debug_asset_path(self, path):
87+
def get_debug_asset_path(self, path: str) -> str | None:
8688
"""Make a "live" check to find the uncompiled asset in the filesystem"""
8789
for asset in iter_assets():
8890
if asset.url_path == path:
8991
return asset.absolute_path
92+
return None
9093

91-
def check_asset_path(self, path):
94+
def check_asset_path(self, path: str | None) -> None:
9295
if not path:
9396
raise Http404("Asset not found")
9497

@@ -99,17 +102,18 @@ def check_asset_path(self, path):
99102
raise Http404("Asset is a directory")
100103

101104
@functools.cache
102-
def get_last_modified(self, path):
105+
def get_last_modified(self, path: str) -> str | None:
103106
try:
104107
mtime = os.path.getmtime(path)
105108
except OSError:
106109
mtime = None
107110

108111
if mtime:
109112
return formatdate(mtime, usegmt=True)
113+
return None
110114

111115
@functools.cache
112-
def get_etag(self, path):
116+
def get_etag(self, path: str) -> str:
113117
try:
114118
mtime = os.path.getmtime(path)
115119
except OSError:
@@ -120,10 +124,10 @@ def get_etag(self, path):
120124
return f'"{timestamp:x}-{size:x}"'
121125

122126
@functools.cache
123-
def get_size(self, path):
127+
def get_size(self, path: str) -> int:
124128
return os.path.getsize(path)
125129

126-
def update_headers(self, headers, path):
130+
def update_headers(self, headers: dict, path: str) -> dict:
127131
headers.setdefault("Access-Control-Allow-Origin", "*")
128132

129133
# Always vary on Accept-Encoding
@@ -165,7 +169,7 @@ def update_headers(self, headers, path):
165169

166170
return headers
167171

168-
def is_immutable(self, path):
172+
def is_immutable(self, path: str) -> bool:
169173
"""
170174
Determine whether an asset looks like it is immutable.
171175
@@ -182,14 +186,14 @@ def is_immutable(self, path):
182186

183187
return False
184188

185-
def get_encoded_path(self, path):
189+
def get_encoded_path(self, path: str) -> str | None:
186190
"""
187191
If the client supports compression, return the path to the compressed file.
188192
Otherwise, return the original path.
189193
"""
190194
accept_encoding = self.request.headers.get("Accept-Encoding")
191195
if not accept_encoding:
192-
return
196+
return None
193197

194198
if "br" in accept_encoding:
195199
br_path = path + ".br"
@@ -200,15 +204,16 @@ def get_encoded_path(self, path):
200204
gzip_path = path + ".gz"
201205
if os.path.exists(gzip_path):
202206
return gzip_path
207+
return None
203208

204-
def get_redirect_response(self, path):
209+
def get_redirect_response(self, path: str) -> ResponseRedirect | None:
205210
"""If the asset is not found, try to redirect to the fingerprinted path"""
206211
fingerprinted_url_path = get_fingerprinted_url_path(path)
207212

208213
if not fingerprinted_url_path or fingerprinted_url_path == path:
209214
# Don't need to redirect if there is no fingerprinted path,
210215
# or we're already looking at it.
211-
return
216+
return None
212217

213218
from .urls import AssetsRouter
214219

@@ -221,7 +226,7 @@ def get_redirect_response(self, path):
221226
},
222227
)
223228

224-
def get_conditional_response(self, path):
229+
def get_conditional_response(self, path: str) -> ResponseNotModified | None:
225230
"""
226231
Support conditional requests (HTTP 304 response) based on ETag and Last-Modified headers.
227232
"""
@@ -241,8 +246,9 @@ def get_conditional_response(self, path):
241246
response = ResponseNotModified()
242247
response.headers = self.update_headers(response.headers, path)
243248
return response
249+
return None
244250

245-
def get_range_response(self, path):
251+
def get_range_response(self, path: str) -> Response | StreamingResponse | None:
246252
"""
247253
Support range requests (HTTP 206 response).
248254
"""

0 commit comments

Comments
 (0)