Skip to content

Attempt to improve liskov substitution principle error #128

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

Merged
merged 3 commits into from
Nov 12, 2022

Conversation

daehan-koreapool
Copy link
Contributor

Background

Goal

  • Start a discussion in an attempt to improve PyCardano code quality
  • I would love to hear your opinion on this, @cffls.
$ make qa
poetry run flake8 pycardano
poetry run mypy --install-types --non-interactive pycardano
pycardano/network.py:21: error: Return type "int" of "to_primitive" incompatible with return type "Primitive" in supertype "CBORSerializable"
pycardano/network.py:25: error: Argument 1 of "from_primitive" is incompatible with supertype "CBORSerializable"; supertype defines the argument type as "Primitive"
pycardano/network.py:25: note: This violates the Liskov substitution principle
pycardano/network.py:25: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
Found 2 errors in 1 file (checked 10 source files)
make: *** [Makefile:65: qa] Error 1

@daehan-koreapool daehan-koreapool added the enhancement New feature or request label Nov 9, 2022
@daehan-koreapool daehan-koreapool self-assigned this Nov 9, 2022
@cffls
Copy link
Collaborator

cffls commented Nov 9, 2022

Thanks @daehan-koreapool for initiating this convo! Not sure why it is complaining line 21. The return type (int) is more restrictive than the super class (Primitive), even the example in the guide demonstrated that more restrictive return type is allowed.

Regarding line 25, it makes sense to change the type of value to Primitive, and add a check at line 26 to make sure the value is an integer, otherwise raise an exception. This will fail early when the wrong input is passed in, and prevent obscure bugs.

@daehan-koreapool
Copy link
Contributor Author

daehan-koreapool commented Nov 11, 2022

I tried making bunch of changes on serialization.Primitive type since this is where most of the type warnings start from, and I noticed few things.

Returning TypeVar without any method parameters raises an error

pycardano/serialization.py:175: error: A function returning TypeVar should receive at least one argument containing the same Typevar
pycardano/serialization.py:175: note: Consider using the upper bound "Union[bytes, bytearray, str, int, float, Decimal, bool, None, Tuple[Any, ...], List[Any], IndefiniteList, Dict[Any, Any], defaultdict[Any, Any], OrderedDict[Any, Any], datetime, Pattern[Any], Any, Any, Set[Any], FrozenSet[Any]]" instead

For a simple method which only returns a generic Primitive type without taking any parameters, mypy cannot infer what value to return.

I also tried defining a generic type with a looser upper bound, but this raises the above error message once agin since we are still using a TypeVar.

Primitive = TypeVar(
    "Primitive",
    bound=Union[
        bytes,
        bytearray,
        str,
        int,
        float,
        Decimal,
        bool,
        None,
        tuple,
        list,
        IndefiniteList,
        dict,
        defaultdict,
        OrderedDict,
        # type(undefined),
        datetime,
        re.Pattern,
        CBORSimpleValue,
        CBORTag,
        set,
        frozenset,
    ],
)

Primitive TypeVar generic type could be a Union type.

With a TypeVar generic type, only a single primitive type can exist in a function scope because how TypeVar works in Python.
Realistically, we should allow multiple CBOR primitive types to exist within a function scope and a Union type represents this better than more strict TypeVar generic type.

Also, when we define more simple Primitive = Union[...], mypy now understands a method can return either of the primitive types.
So, the following error message is satisfied if we use an Union type:

pycardano/network.py:21: error: Return type "int" of "to_primitive" incompatible with return type "Primitive" in supertype "CBORSerializable"

Regarding line 25, it makes sense to change the type of value to Primitive, and add a check at line 26 to make sure the value is an integer, otherwise raise an exception. This will fail early when the wrong input is passed in, and prevent obscure bugs.

Lastly, I do agree and mypy seems to be happy with this approach.

I'll try making few changes and push code changes for you to review through @cffls

@cffls
Copy link
Collaborator

cffls commented Nov 11, 2022

Thanks @daehan-koreapool , changing Primitive to a union type makes sense to me.

UPDATE: include serialization.py in mypy test

UPDATE: Primitive is a union type now

UPDATE: f-string formatted values are explicitly converted into strings

REMOVE: field_sorter() had no real usage

UPDATE: from_primitive() takes a generalized Primitive value

UPDATE: narrow Primitive value type within from_primitive()

FIX: BlockFrostChainContext constructor to have an empty string default value
@codecov-commenter
Copy link

codecov-commenter commented Nov 12, 2022

Codecov Report

Merging #128 (652ca84) into main (7787695) will increase coverage by 0.02%.
The diff coverage is 88.23%.

@@            Coverage Diff             @@
##             main     #128      +/-   ##
==========================================
+ Coverage   85.92%   85.95%   +0.02%     
==========================================
  Files          25       25              
  Lines        2693     2698       +5     
  Branches      518      519       +1     
==========================================
+ Hits         2314     2319       +5     
+ Misses        286      285       -1     
- Partials       93       94       +1     
Impacted Files Coverage Δ
pycardano/backend/blockfrost.py 33.61% <ø> (ø)
pycardano/serialization.py 86.89% <83.33%> (-0.77%) ⬇️
pycardano/network.py 100.00% <100.00%> (+15.38%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@daehan-koreapool daehan-koreapool marked this pull request as ready for review November 12, 2022 06:35
Copy link
Collaborator

@cffls cffls left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work as always. Thank you @daehan-koreapool !

@daehan-koreapool daehan-koreapool merged commit c5ef3bc into main Nov 12, 2022
@daehan-koreapool daehan-koreapool deleted the liskov-substitution-principle-improvement branch November 16, 2022 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants