Skip to content

Commit dbb237f

Browse files
committed
Add typing annotations
Initial draft courtesy of `monkeytype`: ```shell .venv/bin/uv pip install -e plone.api[test] monkeytype zope.testrunner monkeytype run .venv/bin/zope-testrunner --path plone.api/src/ for module in `monkeytype list-modules`; do monkeytype apply $module || true > /dev/null;done ```
1 parent 0bd280e commit dbb237f

17 files changed

+255
-109
lines changed

.meta.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ commit-id = "2.2.2"
99
codespell_extra_lines = """
1010
exclude: docs/locale/.*.pot
1111
"""
12+
extra_lines = """
13+
- repo: https://github.com/pre-commit/mirrors-mypy
14+
rev: v1.18.2
15+
hooks:
16+
- id: mypy
17+
additional_dependencies:
18+
- types-decorator
19+
exclude: docs
20+
"""
1221

1322
[pyproject]
1423
check_manifest_ignores = """

.pre-commit-config.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ repos:
7676
hooks:
7777
- id: i18ndude
7878

79+
- repo: https://github.com/pre-commit/mirrors-mypy
80+
rev: v1.18.2
81+
hooks:
82+
- id: mypy
83+
additional_dependencies:
84+
- types-decorator
85+
exclude: docs
7986

8087
##
8188
# Add extra configuration options in .meta.toml:

news/+a04874c0.internal.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add typing annotations [@ale-rt]

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"plone.dexterity",
3737
"plone.i18n",
3838
"plone.registry",
39+
"plone.supermodel",
3940
"plone.uuid",
4041
"zope.globalrequest",
4142
"Products.CMFCore",

src/plone/api/addon.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from Products.CMFPlone.interfaces import INonInstallable
1212
from Products.CMFPlone.utils import get_installer
1313
from Products.GenericSetup import EXTENSION
14+
from typing import Any
15+
from typing import cast
1416
from typing import Dict
1517
from typing import List
1618
from typing import Tuple
@@ -98,7 +100,7 @@ def _get_non_installable_addons() -> NonInstallableAddons:
98100

99101

100102
@lru_cache(maxsize=1)
101-
def _cached_addons() -> Tuple[Tuple[str, AddonInformation]]:
103+
def _cached_addons() -> Tuple[Tuple[str, AddonInformation], ...]:
102104
"""Return information about add-ons in this installation.
103105
104106
:returns: Tuple of tuples with add-on id and AddonInformation.
@@ -134,7 +136,7 @@ def _cached_addons() -> Tuple[Tuple[str, AddonInformation]]:
134136
profile_type = pid_parts[-1]
135137
if product_id not in addons:
136138
# get some basic information on the product
137-
product = {
139+
product: Dict[str, Any] = {
138140
"id": product_id,
139141
"version": get_version(product_id),
140142
"title": product_id,
@@ -278,7 +280,9 @@ def get(addon: str) -> AddonInformation:
278280
addons = dict(_cached_addons())
279281
if addon not in addons:
280282
raise InvalidParameterError(f"No add-on {addon} found.")
281-
return _update_addon_info(addons.get(addon), _get_installer())
283+
return _update_addon_info(
284+
cast(AddonInformation, addons.get(addon)), _get_installer()
285+
)
282286

283287

284288
@required_parameters("addon")

src/plone/api/content.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from plone.uuid.interfaces import IUUID
1515
from Products.CMFCore.DynamicType import DynamicType
1616
from Products.CMFCore.WorkflowCore import WorkflowException
17+
from typing import Any
1718
from zope.component import ComponentLookupError
1819
from zope.component import getMultiAdapter
1920
from zope.component import getSiteManager
@@ -26,7 +27,7 @@
2627
import uuid
2728

2829

29-
_marker = []
30+
_marker = object()
3031

3132
# Maximum number of attempts to generate a unique random ID
3233
MAX_UNIQUE_ID_ATTEMPTS = 100
@@ -328,7 +329,7 @@ def delete(obj=None, objects=None, check_linkintegrity=True):
328329

329330

330331
@required_parameters("obj")
331-
def get_state(obj=None, default=_marker):
332+
def get_state(obj=None, default: Any = _marker):
332333
"""Get the current workflow state of the object.
333334
334335
:param obj: [required] Object that we want to get the state for.

src/plone/api/portal.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
"""Module that provides various utility methods on the portal level."""
22

33
from Acquisition import aq_inner
4+
from Acquisition import ImplicitAcquisitionWrapper
5+
from datetime import date
6+
from datetime import datetime
7+
from DateTime.DateTime import DateTime
8+
from email.mime.multipart import MIMEMultipart
49
from email.utils import formataddr
510
from email.utils import parseaddr
611
from logging import getLogger
@@ -12,14 +17,21 @@
1217
from Products.CMFCore.interfaces import ISiteRoot
1318
from Products.CMFCore.utils import getToolByName
1419
from Products.statusmessages.interfaces import IStatusMessage
20+
from typing import Any
21+
from typing import List
22+
from typing import Optional
23+
from typing import Union
1524
from zope.component import ComponentLookupError
1625
from zope.component import getUtilitiesFor
1726
from zope.component import getUtility
1827
from zope.component import providedBy
1928
from zope.component.hooks import getSite
2029
from zope.globalrequest import getRequest
30+
from zope.interface.interface import InterfaceClass
2131
from zope.interface.interfaces import IInterface
2232
from zope.schema.interfaces import IVocabularyFactory
33+
from zope.schema.vocabulary import SimpleVocabulary
34+
from ZPublisher.HTTPRequest import HTTPRequest
2335

2436
import datetime as dtime
2537
import re
@@ -52,7 +64,7 @@
5264
MISSING = object()
5365

5466

55-
def get():
67+
def get() -> ImplicitAcquisitionWrapper:
5668
"""Get the Plone portal object out of thin air.
5769
5870
Without the need to import fancy Interfaces and doing multi adapter
@@ -76,7 +88,9 @@ def get():
7688

7789

7890
@required_parameters("context")
79-
def get_navigation_root(context=None):
91+
def get_navigation_root(
92+
context: Optional[ImplicitAcquisitionWrapper] = None,
93+
) -> ImplicitAcquisitionWrapper:
8094
"""Get the navigation root object for the context.
8195
8296
This traverses the path up and returns the nearest navigation root.
@@ -93,7 +107,7 @@ def get_navigation_root(context=None):
93107

94108

95109
@required_parameters("name")
96-
def get_tool(name=None):
110+
def get_tool(name: Optional[str] = None) -> ImplicitAcquisitionWrapper:
97111
"""Get a portal tool in a simple way.
98112
99113
:param name: [required] Name of the tool you want.
@@ -123,11 +137,11 @@ def get_tool(name=None):
123137

124138
@required_parameters("recipient", "subject", "body")
125139
def send_email(
126-
sender=None,
127-
recipient=None,
128-
subject=None,
129-
body=None,
130-
immediate=False,
140+
sender: Optional[str] = None,
141+
recipient: Optional[str] = None,
142+
subject: Optional[str] = None,
143+
body: Optional[Union[MIMEMultipart, str]] = None,
144+
immediate: bool = False,
131145
):
132146
"""Send an email.
133147
@@ -171,11 +185,6 @@ def send_email(
171185
# formataddr probably got confused by special characters.
172186
sender = from_address
173187

174-
# If the mail headers are not properly encoded we need to extract
175-
# them and let MailHost manage the encoding.
176-
if isinstance(body, str):
177-
body = body.encode(encoding)
178-
179188
host = get_tool("MailHost")
180189
host.send(
181190
body,
@@ -188,7 +197,11 @@ def send_email(
188197

189198

190199
@required_parameters("datetime")
191-
def get_localized_time(datetime=None, long_format=False, time_only=False):
200+
def get_localized_time(
201+
datetime: Optional[Union[date, DateTime, datetime]] = None,
202+
long_format: bool = False,
203+
time_only: bool = False,
204+
) -> str:
192205
"""Display a date/time in a user-friendly way.
193206
194207
It should be localized to the user's preferred language.
@@ -236,7 +249,11 @@ def get_localized_time(datetime=None, long_format=False, time_only=False):
236249

237250

238251
@required_parameters("message")
239-
def show_message(message=None, request=None, type="info"):
252+
def show_message(
253+
message: Optional[str] = None,
254+
request: Optional[HTTPRequest] = None,
255+
type: str = "info",
256+
):
240257
"""Display a status message.
241258
242259
:param message: [required] Message to show.
@@ -255,7 +272,11 @@ def show_message(message=None, request=None, type="info"):
255272

256273

257274
@required_parameters("name")
258-
def get_registry_record(name=None, interface=None, default=MISSING):
275+
def get_registry_record(
276+
name: Optional[str] = None,
277+
interface: Optional[InterfaceClass] = None,
278+
default: Any = MISSING,
279+
) -> Any:
259280
"""Get a record value from ``plone.app.registry``.
260281
261282
:param name: [required] Name
@@ -322,7 +343,11 @@ def get_registry_record(name=None, interface=None, default=MISSING):
322343

323344

324345
@required_parameters("name", "value")
325-
def set_registry_record(name=None, value=None, interface=None):
346+
def set_registry_record(
347+
name: Optional[str] = None,
348+
value: Any = None,
349+
interface: Optional[InterfaceClass] = None,
350+
):
326351
"""Set a record value in the ``plone.app.registry``.
327352
328353
:param name: [required] Name of the record
@@ -374,7 +399,7 @@ def set_registry_record(name=None, value=None, interface=None):
374399
registry[name] = value
375400

376401

377-
def get_default_language():
402+
def get_default_language() -> str:
378403
"""Return the default language.
379404
380405
:returns: language identifier
@@ -388,7 +413,7 @@ def get_default_language():
388413
return settings.default_language
389414

390415

391-
def get_current_language(context=None):
416+
def get_current_language(context: Optional[ImplicitAcquisitionWrapper] = None) -> str:
392417
"""Return the current negotiated language.
393418
394419
:param context: context object
@@ -405,7 +430,7 @@ def get_current_language(context=None):
405430
)
406431

407432

408-
def translate(msgid, domain="plone", lang=None):
433+
def translate(msgid: str, domain: str = "plone", lang: Optional[str] = None) -> str:
409434
"""Translate a message into a given language.
410435
411436
Default to current negotiated language if no target language specified.
@@ -437,7 +462,9 @@ def translate(msgid, domain="plone", lang=None):
437462

438463

439464
@required_parameters("name")
440-
def get_vocabulary(name=None, context=None):
465+
def get_vocabulary(
466+
name: Optional[str] = None, context: Optional[ImplicitAcquisitionWrapper] = None
467+
) -> SimpleVocabulary:
441468
"""Return a vocabulary object with the given name.
442469
443470
:param name: Name of the vocabulary.
@@ -464,7 +491,7 @@ def get_vocabulary(name=None, context=None):
464491
return vocabulary(context)
465492

466493

467-
def get_vocabulary_names():
494+
def get_vocabulary_names() -> List[str]:
468495
"""Return a list of vocabulary names.
469496
470497
:returns: A sorted list of vocabulary names.

0 commit comments

Comments
 (0)