Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

more mypy #125

Merged
merged 61 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
f6dd31b
start checking mypy and work on some hints
altendky Nov 25, 2021
ac4d446
remove unused imports
altendky Nov 25, 2021
6706e20
some more mypy
altendky Nov 26, 2021
ea40055
more
altendky Jul 10, 2022
bf598ff
fixup
altendky Aug 18, 2022
2293297
little
altendky Aug 18, 2022
271e80a
Merge branch 'main' into mypie
altendky Nov 13, 2023
3a5e0b0
catch up on test hints
altendky Nov 13, 2023
0f3f7a7
more
altendky Nov 13, 2023
78d02f5
fixup
altendky Nov 13, 2023
89b1cfb
more
altendky Nov 13, 2023
5af95bc
Merge branch 'main' into mypie
altendky Nov 14, 2023
45a33a4
py.typed
altendky Nov 21, 2023
a542909
ignores...
altendky Nov 21, 2023
6abbfe9
stuff
altendky Nov 21, 2023
1f51f77
just ignore for now
altendky Nov 21, 2023
c9feb37
note
altendky Nov 21, 2023
ec21d41
Merge branch 'main' into mypie
altendky Dec 6, 2023
a93ce62
tidy
altendky Dec 7, 2023
a377a48
fix
altendky Dec 7, 2023
baeb9df
fix
altendky Dec 7, 2023
01dac30
flake8
altendky Dec 7, 2023
12f2e2e
typing_externsions.TypeGuard
altendky Dec 7, 2023
ba0ea9a
Apply suggestions from code review
altendky Dec 7, 2023
b5d6e23
add newly needed ignore
altendky Dec 7, 2023
7dee522
fix NestedTupleOfBytes
altendky Dec 7, 2023
2d125bb
Merge branch 'main' into mypie
altendky Dec 9, 2023
302813a
`CLVMObjectLike` -> `CLVMStorage`
altendky Dec 9, 2023
502e5e1
tidy hinting in `core_ops`
altendky Dec 9, 2023
9422b0c
unused assignment in tests
altendky Dec 9, 2023
29926ba
typing_extensions.Never
altendky Dec 9, 2023
12571a4
tidy
altendky Dec 10, 2023
90fb89c
tidy
altendky Dec 10, 2023
55a514d
more tidy
altendky Dec 14, 2023
142ecd1
oops
altendky Dec 14, 2023
ae67139
update note
altendky Dec 14, 2023
c76e7d9
shift around
altendky Dec 14, 2023
393e1ba
more
altendky Dec 14, 2023
d77164d
undo
altendky Dec 14, 2023
c3214f6
tidy
altendky Dec 14, 2023
271ff39
tidy
altendky Dec 14, 2023
6bd0720
tidy
altendky Dec 14, 2023
0b30a6a
prepare for more strict mypy
altendky Dec 14, 2023
1a43f5f
enable 'free' mypy constraints
altendky Dec 14, 2023
1f9dccd
no_implicit_reexport
altendky Dec 14, 2023
bdbb737
some untyped generics
altendky Dec 15, 2023
e66b640
almost, but not quite
altendky Dec 15, 2023
8027443
ToCLVMStorage
richardkiss Dec 18, 2023
98e1f9c
break circular import
altendky Dec 19, 2023
8369c45
touchup
altendky Dec 21, 2023
daabb3e
Merge branch 'main' into mypie
altendky Mar 15, 2024
7222f09
future
altendky Mar 19, 2024
23e47d8
less future
altendky Mar 19, 2024
8f05894
`SExp.to(Any)`
altendky Mar 21, 2024
15f4c2e
remove ignores
altendky Mar 22, 2024
f806e68
use `PythonReturnType` for as python return type
altendky Mar 22, 2024
1f3f89b
drop any
altendky Mar 22, 2024
bfe9e84
hmm
altendky Mar 22, 2024
44ca118
Revert "hmm"
altendky Mar 25, 2024
8f7c410
Revert "drop any"
altendky Mar 25, 2024
7a956fc
Revert "use `PythonReturnType` for as python return type"
altendky Mar 25, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ jobs:
python -m pip install flake8
- name: flake8
run: flake8 clvm tests --max-line-length=120
- name: mypy
run: mypy
- name: Test with pytest
run: |
pytest tests
Expand Down
39 changes: 31 additions & 8 deletions clvm/CLVMObject.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
from __future__ import annotations

import typing


class CLVMStorage(typing.Protocol):
# It's not clear if it is possible to express the exclusivity without maybe
# restructuring all the classes, such as having a separate instance for each
# of the atom and pair cases and hinting a union of a protocol of each type.
atom: typing.Optional[bytes]
pair: typing.Optional[PairType]
altendky marked this conversation as resolved.
Show resolved Hide resolved


PairType = typing.Tuple[CLVMStorage, CLVMStorage]


_T_CLVMObject = typing.TypeVar("_T_CLVMObject", bound="CLVMObject")


class CLVMObject:
"""
This class implements the CLVM Object protocol in the simplest possible way,
Expand All @@ -11,19 +27,26 @@ class CLVMObject:

# this is always a 2-tuple of an object implementing the CLVM object
# protocol.
pair: typing.Optional[typing.Tuple[typing.Any, typing.Any]]
pair: typing.Optional[PairType]
__slots__ = ["atom", "pair"]

def __new__(class_, v):
if isinstance(v, CLVMObject):
@staticmethod
def __new__(
class_: typing.Type[_T_CLVMObject],
v: typing.Union[_T_CLVMObject, bytes, PairType],
) -> _T_CLVMObject:
if isinstance(v, class_):
return v
# mypy does not realize that the isinstance check is type narrowing like this
narrowed_v: typing.Union[bytes, PairType] = v # type: ignore[assignment]

self = super(CLVMObject, class_).__new__(class_)
if isinstance(v, tuple):
if len(v) != 2:
raise ValueError("tuples must be of size 2, cannot create CLVMObject from: %s" % str(v))
self.pair = v
if isinstance(narrowed_v, tuple):
if len(narrowed_v) != 2:
raise ValueError("tuples must be of size 2, cannot create CLVMObject from: %s" % str(narrowed_v))
self.pair = narrowed_v
self.atom = None
else:
self.atom = v
self.atom = narrowed_v
self.pair = None
return self
10 changes: 9 additions & 1 deletion clvm/EvalError.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
from clvm.SExp import SExp


class EvalError(Exception):
def __init__(self, message: str, sexp):
def __init__(self, message: str, sexp: SExp) -> None:
super().__init__(message)
self._sexp = sexp
78 changes: 45 additions & 33 deletions clvm/SExp.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import io
Quexington marked this conversation as resolved.
Show resolved Hide resolved
import typing

import typing_extensions

from .as_python import as_python
from .CLVMObject import CLVMObject
from .CLVMObject import CLVMObject, CLVMStorage

from .EvalError import EvalError

Expand All @@ -16,27 +17,28 @@

CastableType = typing.Union[
"SExp",
"CLVMObject",
CLVMStorage,
typing.SupportsBytes,
bytes,
str,
int,
None,
list,
typing.Tuple[typing.Any, typing.Any],
typing.Sequence["CastableType"],
typing.Tuple["CastableType", "CastableType"],
]


NULL = b""


def looks_like_clvm_object(o: typing.Any) -> bool:
def looks_like_clvm_object(o: typing.Any) -> typing_extensions.TypeGuard[CLVMStorage]:
Quexington marked this conversation as resolved.
Show resolved Hide resolved
d = dir(o)
return "atom" in d and "pair" in d


# this function recognizes some common types and turns them into plain bytes,
def convert_atom_to_bytes(
v: typing.Union[bytes, str, int, None, list],
v: typing.Union[bytes, str, int, None, typing.List[typing_extensions.Never], typing.SupportsBytes],
altendky marked this conversation as resolved.
Show resolved Hide resolved
) -> bytes:

if isinstance(v, bytes):
Expand All @@ -55,10 +57,15 @@ def convert_atom_to_bytes(
raise ValueError("can't cast %s (%s) to bytes" % (type(v), v))


ValType = typing.Union["SExp", CastableType]
StackType = typing.List[ValType]


# returns a clvm-object like object
@typing.no_type_check
def to_sexp_type(
v: CastableType,
):
) -> CLVMStorage:
stack = [v]
ops = [(0, None)] # convert

Expand Down Expand Up @@ -115,6 +122,9 @@ def to_sexp_type(
return stack[0]


_T_SExp = typing.TypeVar("_T_SExp", bound="SExp")


class SExp:
"""
SExp provides higher level API on top of any object implementing the CLVM
Expand All @@ -129,86 +139,88 @@ class SExp:
elements implementing the CLVM object protocol.
Exactly one of "atom" and "pair" must be None.
"""
true: "SExp"
false: "SExp"
__null__: "SExp"
true: typing.ClassVar["SExp"]
false: typing.ClassVar["SExp"]
__null__: typing.ClassVar["SExp"]

# the underlying object implementing the clvm object protocol
atom: typing.Optional[bytes]

# this is a tuple of the otherlying CLVMObject-like objects. i.e. not
# SExp objects with higher level functions, or None
pair: typing.Optional[typing.Tuple[typing.Any, typing.Any]]
pair: typing.Optional[typing.Tuple[CLVMStorage, CLVMStorage]]

def __init__(self, obj):
def __init__(self, obj: CLVMStorage) -> None:
self.atom = obj.atom
self.pair = obj.pair

# this returns a tuple of two SExp objects, or None
def as_pair(self) -> typing.Tuple["SExp", "SExp"]:
def as_pair(self) -> typing.Optional[typing.Tuple["SExp", "SExp"]]:
pair = self.pair
if pair is None:
return pair
return (self.__class__(pair[0]), self.__class__(pair[1]))

# TODO: deprecate this. Same as .atom property
def as_atom(self):
def as_atom(self) -> typing.Optional[bytes]:
return self.atom

def listp(self):
def listp(self) -> bool:
return self.pair is not None

def nullp(self):
def nullp(self) -> bool:
v = self.atom
return v is not None and len(v) == 0

def as_int(self):
def as_int(self) -> int:
if self.atom is None:
raise TypeError("Unable to convert a pair to an int")
return int_from_bytes(self.atom)

def as_bin(self):
def as_bin(self) -> bytes:
f = io.BytesIO()
sexp_to_stream(self, f)
return f.getvalue()

@classmethod
def to(class_, v: CastableType) -> "SExp":
if isinstance(v, class_):
def to(cls: typing.Type[_T_SExp], v: CastableType) -> _T_SExp:
if isinstance(v, cls):
return v

if looks_like_clvm_object(v):
return class_(v)
return cls(v)

# this will lazily convert elements
return class_(to_sexp_type(v))
return cls(to_sexp_type(v))

def cons(self, right):
def cons(self: _T_SExp, right: _T_SExp) -> _T_SExp:
return self.to((self, right))

def first(self):
def first(self: _T_SExp) -> _T_SExp:
pair = self.pair
if pair:
return self.__class__(pair[0])
raise EvalError("first of non-cons", self)

def rest(self):
def rest(self: _T_SExp) -> _T_SExp:
pair = self.pair
if pair:
return self.__class__(pair[1])
raise EvalError("rest of non-cons", self)

@classmethod
def null(class_):
def null(class_) -> "SExp":
return class_.__null__

def as_iter(self):
def as_iter(self: _T_SExp) -> typing.Iterator[_T_SExp]:
v = self
while not v.nullp():
yield v.first()
v = v.rest()

def __eq__(self, other: CastableType):
def __eq__(self, other: object) -> bool:
try:
other = self.to(other)
other = self.to(typing.cast(CastableType, other))
to_compare_stack = [(self, other)]
while to_compare_stack:
s1, s2 = to_compare_stack.pop()
Expand All @@ -226,21 +238,21 @@ def __eq__(self, other: CastableType):
except ValueError:
return False

def list_len(self):
def list_len(self) -> int:
v = self
size = 0
while v.listp():
size += 1
v = v.rest()
return size

def as_python(self):
def as_python(self) -> typing.Any:
altendky marked this conversation as resolved.
Show resolved Hide resolved
return as_python(self)

def __str__(self):
def __str__(self) -> str:
return self.as_bin().hex()

def __repr__(self):
def __repr__(self) -> str:
return "%s(%s)" % (self.__class__.__name__, str(self))


Expand Down
92 changes: 58 additions & 34 deletions clvm/as_python.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,61 @@
def as_python(sexp):
def _roll(op_stack, val_stack):
v1 = val_stack.pop()
v2 = val_stack.pop()
val_stack.append(v1)
val_stack.append(v2)

def _make_tuple(op_stack, val_stack):
left = val_stack.pop()
right = val_stack.pop()
if right == b"":
val_stack.append([left])
elif isinstance(right, list):
v = [left] + right
val_stack.append(v)
else:
val_stack.append((left, right))

def _as_python(op_stack, val_stack):
t = val_stack.pop()
pair = t.as_pair()
if pair:
left, right = pair
op_stack.append(_make_tuple)
op_stack.append(_as_python)
op_stack.append(_roll)
op_stack.append(_as_python)
val_stack.append(left)
val_stack.append(right)
else:
val_stack.append(t.as_atom())

op_stack = [_as_python]
val_stack = [sexp]
from __future__ import annotations

from typing import Any, Callable, List, Tuple, TYPE_CHECKING, Union

if TYPE_CHECKING:
from clvm.SExp import SExp

OpCallable = Callable[["OpStackType", "ValStackType"], None]

PythonReturnType = Union[bytes, Tuple["PythonReturnType", "PythonReturnType"], List["PythonReturnType"]]

ValType = Union["SExp", PythonReturnType]
ValStackType = List[ValType]

OpStackType = List[OpCallable]


def _roll(op_stack: OpStackType, val_stack: ValStackType) -> None:
v1 = val_stack.pop()
v2 = val_stack.pop()
val_stack.append(v1)
val_stack.append(v2)


MakeTupleValStackType = List[Union[bytes, Tuple[object, object], "MakeTupleValStackType"]]


def _make_tuple(op_stack: OpStackType, val_stack: ValStackType) -> None:
left: PythonReturnType = val_stack.pop() # type: ignore[assignment]
right: PythonReturnType = val_stack.pop() # type: ignore[assignment]
if right == b"":
val_stack.append([left])
elif isinstance(right, list):
v = [left] + right
val_stack.append(v)
else:
val_stack.append((left, right))


def _as_python(op_stack: OpStackType, val_stack: ValStackType) -> None:
t: SExp = val_stack.pop() # type: ignore[assignment]
pair = t.as_pair()
if pair:
left, right = pair
op_stack.append(_make_tuple)
op_stack.append(_as_python)
op_stack.append(_roll)
op_stack.append(_as_python)
val_stack.append(left)
val_stack.append(right)
else:
# we know that t.atom is not None here because the pair is None
val_stack.append(t.atom) # type:ignore[arg-type]


def as_python(sexp: SExp) -> Any:
op_stack: OpStackType = [_as_python]
val_stack: ValStackType = [sexp]
while op_stack:
op_f = op_stack.pop()
op_f(op_stack, val_stack)
Expand Down
Loading
Loading