Skip to content

Commit 19fed0f

Browse files
committed
Introduce optional static typing
Type hints are added within the comment-based syntax that should work with Python 2.
1 parent b4944df commit 19fed0f

File tree

6 files changed

+153
-20
lines changed

6 files changed

+153
-20
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ dist/
77
*.egg/
88
*.eggs/
99

10+
.mypy_cache
11+
1012
doc/_build/
1113

1214
.coverage*

form/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,14 @@
1414
4*p1.p2*p3.p4-4*p1.p3*p2.p4+4*p1.p4*p2.p3
1515
"""
1616

17+
if False:
18+
from typing import Optional, Sequence, Union # noqa: F401
19+
1720
from .formlink import FormError, FormLink # noqa: F401
1821

1922

2023
def open(args=None, keep_log=False):
24+
# type: (Optional[Union[str, Sequence[str]]], Union[bool, int]) -> FormLink
2125
"""Open a connection to FORM and return a link object.
2226
2327
Open a connection to a new FORM process and return a

form/datapath.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,26 @@
44
import pkgutil
55
import sys
66

7+
if False:
8+
from typing import Optional # noqa: F401
9+
710

811
def get_data_path(package, resource):
12+
# type: (str, str) -> str
913
"""Return the full file path of a resource of a package."""
1014
loader = pkgutil.get_loader(package)
1115
if loader is None or not hasattr(loader, 'get_data'):
12-
return None
16+
raise PackageResourceError("Failed to load package: '{0}'".format(
17+
package))
1318
mod = sys.modules.get(package) or loader.load_module(package)
1419
if mod is None or not hasattr(mod, '__file__'):
15-
return None
20+
raise PackageResourceError("Failed to load module: '{0}'".format(
21+
package))
1622
parts = resource.split('/')
1723
parts.insert(0, os.path.dirname(mod.__file__))
1824
resource_name = os.path.join(*parts)
1925
return resource_name
26+
27+
28+
class PackageResourceError(IOError):
29+
"""Package resource not found."""

form/formlink.py

+109-16
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
from .io import PushbackReader, set_nonblock
1313
from .six import PY32, string_types
1414

15+
if False:
16+
from typing import Any, IO, MutableSequence, Optional, Sequence, Tuple, Union, overload # noqa: E501, F401
17+
if True:
18+
def overload(f): # type: ignore # noqa: D103, F811
19+
return None
20+
1521

1622
class FormLink(object):
1723
"""Connection to a FORM process."""
@@ -25,18 +31,20 @@ class FormLink(object):
2531
_PROMPT = '\n__READY__\n'
2632

2733
def __init__(self, args=None, keep_log=False):
34+
# type: (Optional[Union[str, Sequence[str]]], Union[bool, int]) -> None
2835
"""Open a connection to a FORM process."""
2936
self._closed = True
30-
self._head = None
31-
self._log = None
32-
self._childpid = None
33-
self._formpid = None
34-
self._parentin = None
35-
self._parentout = None
36-
self._loggingin = None
37+
self._head = None # type: Optional[str]
38+
self._log = None # type: Optional[MutableSequence[str]]
39+
self._childpid = None # type: Optional[int]
40+
self._formpid = None # type: Optional[int]
41+
self._parentin = None # type: Optional[PushbackReader]
42+
self._parentout = None # type: Optional[IO[str]]
43+
self._loggingin = None # type: Optional[PushbackReader]
3744
self.open(args, keep_log)
3845

3946
def __del__(self):
47+
# type: () -> None
4048
"""Destructor.
4149
4250
Free the connection to the FORM process if it still exists. Since in
@@ -51,14 +59,17 @@ def __del__(self):
5159
pass
5260

5361
def __enter__(self):
62+
# type: () -> FormLink
5463
"""Enter the runtime context."""
5564
return self
5665

5766
def __exit__(self, exc_type, exc_value, traceback):
67+
# type: (Any, Any, Any) -> None
5868
"""Exit the runtime context."""
5969
self.close()
6070

6171
def open(self, args=None, keep_log=False):
72+
# type: (Optional[Union[str, Sequence[str]]], Union[bool, int]) -> None
6273
"""Open a connection to FORM.
6374
6475
Open a connection to a new FORM process. The opened connection should
@@ -94,11 +105,12 @@ def open(self, args=None, keep_log=False):
94105
args = os.environ['FORM']
95106
else:
96107
args = 'form'
97-
98108
if isinstance(args, string_types):
99109
args = shlex.split(args) # Split the arguments.
100110
elif isinstance(args, (list, tuple)):
101111
args = list(args) # As a modifiable mutable object.
112+
else:
113+
raise TypeError("invalid args = {0}".format(args))
102114

103115
self.close()
104116

@@ -146,8 +158,6 @@ def open(self, args=None, keep_log=False):
146158

147159
set_nonblock(fd_parentin)
148160
set_nonblock(fd_loggingin)
149-
parentin = PushbackReader(parentin)
150-
loggingin = PushbackReader(loggingin)
151161

152162
self._closed = False
153163
self._head = head
@@ -164,9 +174,9 @@ def open(self, args=None, keep_log=False):
164174
parentout.write('#-\n')
165175
self._childpid = pid
166176
self._formpid = formpid
167-
self._parentin = parentin
177+
self._parentin = PushbackReader(parentin)
168178
self._parentout = parentout
169-
self._loggingin = loggingin
179+
self._loggingin = PushbackReader(loggingin)
170180
else:
171181
# child process
172182

@@ -202,6 +212,7 @@ def open(self, args=None, keep_log=False):
202212
os._exit(0)
203213

204214
def close(self):
215+
# type: () -> None
205216
"""Close the connection to FORM.
206217
207218
Close the connection to the FORM process established by :meth:`open`.
@@ -212,7 +223,13 @@ def close(self):
212223
self._close()
213224

214225
def _close(self, term=False, kill=False):
226+
# type: (float, float) -> None
215227
if not self._closed:
228+
assert self._childpid is not None
229+
assert self._formpid is not None
230+
assert self._parentin is not None
231+
assert self._parentout is not None
232+
assert self._loggingin is not None
216233
try:
217234
# We ignore broken pipes.
218235
try:
@@ -236,7 +253,9 @@ def _close(self, term=False, kill=False):
236253
# finish shortly.
237254

238255
def wait(timeout): # timeout <= 0 means no wait
256+
# type: (float) -> bool
239257
# Wait for the child to finish.
258+
assert self._childpid is not None
240259
t = 0.0
241260
dt = 0.01
242261
if timeout > 0:
@@ -281,11 +300,13 @@ def wait(timeout): # timeout <= 0 means no wait
281300
self._loggingin = None
282301

283302
def kill(self):
303+
# type: () -> None
284304
"""Kill the FORM process and close the connection."""
285305
self._close(kill=-1) # Kill it immediately.
286306
# self._close(term=-1, kill=1)
287307

288308
def write(self, script):
309+
# type: (str) -> None
289310
"""Send a script to FORM.
290311
291312
Write the given script to the communication channel to FORM. It could
@@ -296,10 +317,12 @@ def write(self, script):
296317
raise IOError('tried to write to closed connection')
297318
script = script.strip()
298319
if script:
320+
assert self._parentout is not None
299321
self._parentout.write(script)
300322
self._parentout.write('\n')
301323

302324
def flush(self):
325+
# type: () -> None
303326
"""Flush the channel to FORM.
304327
305328
Flush the communication channel to FORM. Because :meth:`write` is
@@ -308,9 +331,71 @@ def flush(self):
308331
"""
309332
if self._closed:
310333
raise IOError('tried to flush closed connection')
334+
assert self._parentout is not None
311335
self._parentout.flush()
312336

313-
def read(self, *names):
337+
@overload
338+
def read(self, name): # noqa: D102
339+
# type: (str) -> str
340+
pass
341+
342+
@overload # noqa: F811
343+
def read(self, name1, name2): # noqa: D102
344+
# type: (str, str) -> Tuple[str, str]
345+
pass
346+
347+
@overload # noqa: F811
348+
def read(self, name1, name2, name3): # noqa: D102
349+
# type: (str, str, str) -> Tuple[str, str, str]
350+
pass
351+
352+
@overload # noqa: F811
353+
def read(self, name1, name2, name3, name4): # noqa: D102
354+
# type: (str, str, str, str) -> Tuple[str, str, str, str]
355+
pass
356+
357+
@overload # noqa: F811
358+
def read(self, name1, name2, name3, name4, name5): # noqa: D102
359+
# type: (str, str, str, str, str) -> Tuple[str, str, str, str, str]
360+
pass
361+
362+
@overload # noqa: F811
363+
def read(self, name1, name2, name3, name4, name5, name6): # noqa: D102
364+
# type: (str, str, str, str, str, str) -> Tuple[str, str, str, str, str, str] # noqa: E501
365+
pass
366+
367+
@overload # noqa: F811
368+
def read(self, name1, name2, name3, name4, name5, name6, name7): # noqa: D102, E501
369+
# type: (str, str, str, str, str, str, str) -> Tuple[str, str, str, str, str, str, str] # noqa: E501
370+
pass
371+
372+
@overload # noqa: F811
373+
def read(self, name1, name2, name3, name4, name5, name6, name7, name8): # noqa: D102, E501
374+
# type: (str, str, str, str, str, str, str, str) -> Tuple[str, str, str, str, str, str, str, str] # noqa: E501
375+
pass
376+
377+
@overload # noqa: F811
378+
def read(self, name1, name2, name3, name4, name5, name6, name7, name8, name9): # noqa: D102, E501
379+
# type: (str, str, str, str, str, str, str, str, str) -> Tuple[str, str, str, str, str, str, str, str, str] # noqa: E501
380+
pass
381+
382+
@overload # noqa: F811
383+
def read(self, names): # noqa: D102
384+
# type: (Sequence[str]) -> Sequence[str]
385+
pass
386+
387+
@overload # noqa: F811
388+
def read(self, *names): # noqa: D102
389+
# type: (str) -> Sequence[str]
390+
pass
391+
392+
@overload # noqa: F811
393+
def read(self, *names): # noqa: D102
394+
# type: (Any) -> Any
395+
pass
396+
397+
def read(self, *names): # type: ignore # noqa: F811
398+
# type: (Any) -> Any
314399
r"""Read results from FORM.
315400
316401
Wait for a response of FORM to obtain the results specified by
@@ -373,6 +458,10 @@ def read(self, *names):
373458
if any(not isinstance(x, string_types) for x in names):
374459
return [self.read(x) for x in names]
375460

461+
assert self._parentin is not None
462+
assert self._parentout is not None
463+
assert self._loggingin is not None
464+
376465
for e in names:
377466
if len(e) >= 2 and e[0] == '`' and e[-1] == "'":
378467
self._parentout.write(
@@ -461,22 +550,26 @@ def read(self, *names):
461550

462551
@property
463552
def closed(self):
553+
# type: () -> bool
464554
"""Return True if the connection is closed."""
465555
return self._closed
466556

467557
@property
468558
def head(self):
559+
# type: () -> str
469560
"""Return the first line of the FORM output."""
561+
assert self._head is not None
470562
return self._head
471563

472564
@property
473565
def _dateversion(self):
566+
# type: () -> int
474567
"""Return the build/revision date as an integer "yyyymmdd"."""
475568
import re
476569
if self._head:
477-
m = re.search(r'(?<=\()(.*)(?=\))', self._head)
478-
if m:
479-
s = re.split(r'[, ]+', m.group(0))
570+
ma = re.search(r'(?<=\()(.*)(?=\))', self._head)
571+
if ma:
572+
s = re.split(r'[, ]+', ma.group(0))
480573
if len(s) >= 3:
481574
# month
482575
month_names = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',

form/io.py

+10
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
import fcntl
44
import os
55

6+
if False:
7+
from typing import IO # noqa: F401
8+
69

710
def set_nonblock(fd):
11+
# type: (int) -> None
812
"""Set the given file descriptor to non-blocking mode."""
913
fcntl.fcntl(fd,
1014
fcntl.F_SETFL,
@@ -15,25 +19,30 @@ class PushbackReader(object):
1519
"""Wrapper for streams with push back operations."""
1620

1721
def __init__(self, raw):
22+
# type: (IO[str]) -> None
1823
"""Initialize the reader."""
1924
self._raw = raw
2025
self._buf = ''
2126

2227
def close(self):
28+
# type: () -> None
2329
"""Close the stream."""
2430
self._raw.close()
2531

2632
def fileno(self):
33+
# type: () -> int
2734
"""Return the file descriptor."""
2835
return self._raw.fileno()
2936

3037
def read(self):
38+
# type: () -> str
3139
"""Read data from the stream."""
3240
s = self._buf + self._raw.read()
3341
self._buf = ''
3442
return s
3543

3644
def unread(self, s):
45+
# type: (str) -> None
3746
"""Push back a string.
3847
3948
Push back the given string to the internal buffer, which will be used
@@ -42,6 +51,7 @@ def unread(self, s):
4251
self._buf = s + self._buf
4352

4453
def read0(self):
54+
# type: () -> str
4555
"""Read the pushed-back string.
4656
4757
Read a string pushed-back by a previous ``unread()``. No call to

setup.cfg

+16-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,22 @@ test=nosetests
44
[bdist_wheel]
55
universal=1
66

7-
[flake8]
8-
ignore=D203
7+
[mypy]
8+
follow_imports=normal
9+
ignore_missing_imports=True
10+
# strict mode
11+
disallow_untyped_calls=True
12+
disallow_untyped_defs=True
13+
disallow_incomplete_defs=True
14+
check_untyped_defs=True
15+
disallow_subclassing_any=True
16+
disallow_untyped_decorators=True
17+
warn_redundant_casts=True
18+
warn_return_any=True
19+
warn_unused_ignores=True
20+
warn_unused_configs=True
21+
no_implicit_optional=True
22+
strict_optional=True
923

1024
[nosetests]
1125
detailed-errors=1

0 commit comments

Comments
 (0)