Skip to content

Commit f11ec00

Browse files
committed
Add type hints for contents module
Types make it easier to understand the code and improve autocompletion in IDEs.
1 parent f2ab4a1 commit f11ec00

File tree

1 file changed

+53
-42
lines changed

1 file changed

+53
-42
lines changed

pelican/contents.py

Lines changed: 53 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
import re
77
from datetime import timezone
88
from html import unescape
9-
from urllib.parse import unquote, urljoin, urlparse, urlunparse
9+
from typing import Any, Dict, Optional, Set
10+
from urllib.parse import ParseResult, unquote, urljoin, urlparse, urlunparse
1011

1112
try:
1213
from zoneinfo import ZoneInfo
@@ -15,7 +16,7 @@
1516

1617

1718
from pelican.plugins import signals
18-
from pelican.settings import DEFAULT_CONFIG
19+
from pelican.settings import DEFAULT_CONFIG, Settings
1920
from pelican.utils import (
2021
deprecated_attribute,
2122
memoized,
@@ -44,12 +45,20 @@ class Content:
4445
4546
"""
4647

48+
default_template = None
49+
mandatory_properties = ()
50+
4751
@deprecated_attribute(old="filename", new="source_path", since=(3, 2, 0))
4852
def filename():
4953
return None
5054

5155
def __init__(
52-
self, content, metadata=None, settings=None, source_path=None, context=None
56+
self,
57+
content: str,
58+
metadata: Optional[Dict[str, Any]] = None,
59+
settings: Optional[Settings] = None,
60+
source_path: Optional[str] = None,
61+
context: Optional[Dict[Any, Any]] = None,
5362
):
5463
if metadata is None:
5564
metadata = {}
@@ -156,10 +165,10 @@ def __init__(
156165

157166
signals.content_object_init.send(self)
158167

159-
def __str__(self):
168+
def __str__(self) -> str:
160169
return self.source_path or repr(self)
161170

162-
def _has_valid_mandatory_properties(self):
171+
def _has_valid_mandatory_properties(self) -> bool:
163172
"""Test mandatory properties are set."""
164173
for prop in self.mandatory_properties:
165174
if not hasattr(self, prop):
@@ -169,7 +178,7 @@ def _has_valid_mandatory_properties(self):
169178
return False
170179
return True
171180

172-
def _has_valid_save_as(self):
181+
def _has_valid_save_as(self) -> bool:
173182
"""Return true if save_as doesn't write outside output path, false
174183
otherwise."""
175184
try:
@@ -190,7 +199,7 @@ def _has_valid_save_as(self):
190199

191200
return True
192201

193-
def _has_valid_status(self):
202+
def _has_valid_status(self) -> bool:
194203
if hasattr(self, "allowed_statuses"):
195204
if self.status not in self.allowed_statuses:
196205
logger.error(
@@ -204,7 +213,7 @@ def _has_valid_status(self):
204213
# if undefined we allow all
205214
return True
206215

207-
def is_valid(self):
216+
def is_valid(self) -> bool:
208217
"""Validate Content"""
209218
# Use all() to not short circuit and get results of all validations
210219
return all(
@@ -216,7 +225,7 @@ def is_valid(self):
216225
)
217226

218227
@property
219-
def url_format(self):
228+
def url_format(self) -> Dict[str, Any]:
220229
"""Returns the URL, formatted with the proper values"""
221230
metadata = copy.copy(self.metadata)
222231
path = self.metadata.get("path", self.get_relative_source_path())
@@ -232,19 +241,19 @@ def url_format(self):
232241
)
233242
return metadata
234243

235-
def _expand_settings(self, key, klass=None):
244+
def _expand_settings(self, key: str, klass: Optional[str] = None) -> str:
236245
if not klass:
237246
klass = self.__class__.__name__
238247
fq_key = (f"{klass}_{key}").upper()
239248
return str(self.settings[fq_key]).format(**self.url_format)
240249

241-
def get_url_setting(self, key):
250+
def get_url_setting(self, key: str) -> str:
242251
if hasattr(self, "override_" + key):
243252
return getattr(self, "override_" + key)
244253
key = key if self.in_default_lang else "lang_%s" % key
245254
return self._expand_settings(key)
246255

247-
def _link_replacer(self, siteurl, m):
256+
def _link_replacer(self, siteurl: str, m: re.Match) -> str:
248257
what = m.group("what")
249258
value = urlparse(m.group("value"))
250259
path = value.path
@@ -272,15 +281,15 @@ def _link_replacer(self, siteurl, m):
272281
# XXX Put this in a different location.
273282
if what in {"filename", "static", "attach"}:
274283

275-
def _get_linked_content(key, url):
284+
def _get_linked_content(key: str, url: ParseResult) -> Optional[Content]:
276285
nonlocal value
277286

278-
def _find_path(path):
287+
def _find_path(path: str) -> Optional[Content]:
279288
if path.startswith("/"):
280289
path = path[1:]
281290
else:
282291
# relative to the source path of this content
283-
path = self.get_relative_source_path(
292+
path = self.get_relative_source_path( # type: ignore
284293
os.path.join(self.relative_dir, path)
285294
)
286295
return self._context[key].get(path, None)
@@ -324,7 +333,7 @@ def _find_path(path):
324333
linked_content = _get_linked_content(key, value)
325334
if linked_content:
326335
if what == "attach":
327-
linked_content.attach_to(self)
336+
linked_content.attach_to(self) # type: ignore
328337
origin = joiner(siteurl, linked_content.url)
329338
origin = origin.replace("\\", "/") # for Windows paths.
330339
else:
@@ -357,9 +366,9 @@ def _find_path(path):
357366
parts[2] = origin
358367
origin = urlunparse(parts)
359368

360-
return "".join((m.group("markup"), m.group("quote"), origin, m.group("quote")))
369+
return "".join((m.group("markup"), m.group("quote"), origin, m.group("quote"))) # type: ignore
361370

362-
def _get_intrasite_link_regex(self):
371+
def _get_intrasite_link_regex(self) -> re.Pattern:
363372
intrasite_link_regex = self.settings["INTRASITE_LINK_REGEX"]
364373
regex = r"""
365374
(?P<markup><[^\>]+ # match tag with all url-value attributes
@@ -370,7 +379,7 @@ def _get_intrasite_link_regex(self):
370379
(?P=quote)""".format(intrasite_link_regex)
371380
return re.compile(regex, re.X)
372381

373-
def _update_content(self, content, siteurl):
382+
def _update_content(self, content: str, siteurl: str) -> str:
374383
"""Update the content attribute.
375384
376385
Change all the relative paths of the content to relative paths
@@ -386,7 +395,7 @@ def _update_content(self, content, siteurl):
386395
hrefs = self._get_intrasite_link_regex()
387396
return hrefs.sub(lambda m: self._link_replacer(siteurl, m), content)
388397

389-
def get_static_links(self):
398+
def get_static_links(self) -> Set[str]:
390399
static_links = set()
391400
hrefs = self._get_intrasite_link_regex()
392401
for m in hrefs.finditer(self._content):
@@ -402,27 +411,27 @@ def get_static_links(self):
402411
path = self.get_relative_source_path(
403412
os.path.join(self.relative_dir, path)
404413
)
405-
path = path.replace("%20", " ")
414+
path = path.replace("%20", " ") # type: ignore
406415
static_links.add(path)
407416
return static_links
408417

409-
def get_siteurl(self):
418+
def get_siteurl(self) -> str:
410419
return self._context.get("localsiteurl", "")
411420

412421
@memoized
413-
def get_content(self, siteurl):
422+
def get_content(self, siteurl: str) -> str:
414423
if hasattr(self, "_get_content"):
415424
content = self._get_content()
416425
else:
417426
content = self._content
418427
return self._update_content(content, siteurl)
419428

420429
@property
421-
def content(self):
430+
def content(self) -> str:
422431
return self.get_content(self.get_siteurl())
423432

424433
@memoized
425-
def get_summary(self, siteurl):
434+
def get_summary(self, siteurl: str) -> str:
426435
"""Returns the summary of an article.
427436
428437
This is based on the summary metadata if set, otherwise truncate the
@@ -441,10 +450,10 @@ def get_summary(self, siteurl):
441450
)
442451

443452
@property
444-
def summary(self):
453+
def summary(self) -> str:
445454
return self.get_summary(self.get_siteurl())
446455

447-
def _get_summary(self):
456+
def _get_summary(self) -> str:
448457
"""deprecated function to access summary"""
449458

450459
logger.warning(
@@ -454,25 +463,25 @@ def _get_summary(self):
454463
return self.summary
455464

456465
@summary.setter
457-
def summary(self, value):
466+
def summary(self, value: str):
458467
"""Dummy function"""
459468
pass
460469

461470
@property
462-
def status(self):
471+
def status(self) -> str:
463472
return self._status
464473

465474
@status.setter
466-
def status(self, value):
475+
def status(self, value: str) -> None:
467476
# TODO maybe typecheck
468477
self._status = value.lower()
469478

470479
@property
471-
def url(self):
480+
def url(self) -> str:
472481
return self.get_url_setting("url")
473482

474483
@property
475-
def save_as(self):
484+
def save_as(self) -> str:
476485
return self.get_url_setting("save_as")
477486

478487
def _get_template(self):
@@ -481,7 +490,9 @@ def _get_template(self):
481490
else:
482491
return self.default_template
483492

484-
def get_relative_source_path(self, source_path=None):
493+
def get_relative_source_path(
494+
self, source_path: Optional[str] = None
495+
) -> Optional[str]:
485496
"""Return the relative path (from the content path) to the given
486497
source_path.
487498
@@ -501,7 +512,7 @@ def get_relative_source_path(self, source_path=None):
501512
)
502513

503514
@property
504-
def relative_dir(self):
515+
def relative_dir(self) -> str:
505516
return posixize_path(
506517
os.path.dirname(
507518
os.path.relpath(
@@ -511,7 +522,7 @@ def relative_dir(self):
511522
)
512523
)
513524

514-
def refresh_metadata_intersite_links(self):
525+
def refresh_metadata_intersite_links(self) -> None:
515526
for key in self.settings["FORMATTED_FIELDS"]:
516527
if key in self.metadata and key != "summary":
517528
value = self._update_content(self.metadata[key], self.get_siteurl())
@@ -534,7 +545,7 @@ class Page(Content):
534545
default_status = "published"
535546
default_template = "page"
536547

537-
def _expand_settings(self, key):
548+
def _expand_settings(self, key: str) -> str:
538549
klass = "draft_page" if self.status == "draft" else None
539550
return super()._expand_settings(key, klass)
540551

@@ -561,7 +572,7 @@ def __init__(self, *args, **kwargs):
561572
if not hasattr(self, "date") and self.status == "draft":
562573
self.date = datetime.datetime.max.replace(tzinfo=self.timezone)
563574

564-
def _expand_settings(self, key):
575+
def _expand_settings(self, key: str) -> str:
565576
klass = "draft" if self.status == "draft" else "article"
566577
return super()._expand_settings(key, klass)
567578

@@ -571,7 +582,7 @@ class Static(Content):
571582
default_status = "published"
572583
default_template = None
573584

574-
def __init__(self, *args, **kwargs):
585+
def __init__(self, *args, **kwargs) -> None:
575586
super().__init__(*args, **kwargs)
576587
self._output_location_referenced = False
577588

@@ -588,18 +599,18 @@ def dst():
588599
return None
589600

590601
@property
591-
def url(self):
602+
def url(self) -> str:
592603
# Note when url has been referenced, so we can avoid overriding it.
593604
self._output_location_referenced = True
594605
return super().url
595606

596607
@property
597-
def save_as(self):
608+
def save_as(self) -> str:
598609
# Note when save_as has been referenced, so we can avoid overriding it.
599610
self._output_location_referenced = True
600611
return super().save_as
601612

602-
def attach_to(self, content):
613+
def attach_to(self, content: Content) -> None:
603614
"""Override our output directory with that of the given content object."""
604615

605616
# Determine our file's new output path relative to the linking
@@ -624,7 +635,7 @@ def attach_to(self, content):
624635

625636
new_url = path_to_url(new_save_as)
626637

627-
def _log_reason(reason):
638+
def _log_reason(reason: str) -> None:
628639
logger.warning(
629640
"The {attach} link in %s cannot relocate "
630641
"%s because %s. Falling back to "

0 commit comments

Comments
 (0)