-
Notifications
You must be signed in to change notification settings - Fork 87
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
Use kw_only=True parameter in dataclass module #673
Conversation
…rds compatibility for older versions of Python, this feature is enabled in Python v3.10 or later versions
Does this preserve the error message in 3.7? |
Yes, developers will get an error like:
|
Please add a unit test to confirm that an error is thrown in previous versions. |
…by positional args on data class
@mvadari I have added the unit tests, would you be able to check this PR again? |
I would add this to the HISTORY.md since the intent is to make auto-complete work with IDEs (which will impact developers) |
Co-authored-by: Jackson Mills <jmills@ripple.com>
@@ -56,3 +56,14 @@ def test_has_multiple_signing_methods_is_invalid(self): | |||
passphrase=_DUMMY_STRING, | |||
seed_hex=_DUMMY_STRING, | |||
) | |||
|
|||
def test_throws_if_positional_args_mixed_with_non_positional_args(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this test here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted a unit test that shows that positional arguments are not permitted in the constructor of ledger objects. that was my intention
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in 0cbc07e
let me know if that looks ok
@@ -72,3 +72,13 @@ def test_valid_seed(self): | |||
transaction=_TRANSACTION, seed=_SEED, key_type=CryptoAlgorithm.SECP256K1 | |||
) | |||
self.assertTrue(request.is_valid()) | |||
|
|||
def test_ctor_has_positional_args(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this test here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, what does ctor mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i shortened constructor to ctor
. i can use the full name
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated in 0cbc07e.
Let me know if that looks ok
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
tests/unit/models/test_utils.py
Outdated
@@ -11,3 +32,24 @@ class TestUtils(TestCase): | |||
def test_kwargs_req(self): | |||
with self.assertRaises(XRPLModelException): | |||
IssuedCurrency(currency, issuer) | |||
|
|||
def test_throws_if_positional_args_mixed_with_non_positional_args(self): | |||
with self.assertRaises(XRPLModelException): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused why this works with >=3.10 - shouldn't it be throwing a different error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, thanks for pointing this out. I had forgotten to remove the decorator @required_kw_args
. It was falling back on the old code. I'll update this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old decorator should still be used in some form, to keep support for the error for <3.10.
tests/unit/models/test_utils.py
Outdated
|
||
class TestUtils(TestCase): | ||
def test_kwargs_req(self): | ||
with self.assertRaises(XRPLModelException): | ||
IssuedCurrency(currency, issuer) | ||
if sys.version_info.major == 3 and sys.version_info.minor < 10: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mvadari I have updated the unit tests to depend on the version of Python
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend writing a helper function to do this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, for consistency (and in case they backport the changes), I'd recommend doing the same check you do in KW_ONLY_DATACLASS
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point
# noinspection PyTypeHints | ||
cls.__init__ = new_init # type: ignore | ||
|
||
# For Python v3.10 and above, the KW_ONLY attribute in data_class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mvadari I came up with this conditional statement to accomodate different versions of python. Does this look good? Do you have any other ideas in mind?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency (and in case they backport the changes), I'd recommend doing the same check you do in KW_ONLY_DATACLASS
tests/unit/models/test_utils.py
Outdated
def test_positional_args_in_model_constructor_throws(self): | ||
if sys.version_info.major == 3 and sys.version_info.minor < 10: | ||
with self.assertRaises(XRPLModelException): | ||
Sign( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use a different RPC for this, since Sign
may be deprecated in the library at some point (it's really only used with an admin connection).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
tests/unit/models/test_utils.py
Outdated
if is_kw_only_attr_defined_in_dataclass(): | ||
with self.assertRaises(XRPLModelException): | ||
IssuedCurrency(currency, issuer) | ||
else: | ||
with self.assertRaises(TypeError): | ||
IssuedCurrency(currency, issuer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend putting this if-else block in a reusable context manager to avoid needing it in every test/avoid repetition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean by a re-usable context manager? Do you mean something like:
def testWrapper(pythonVersionDiscriminator, oldVersion, newVersion):
if pythonVersionDiscriminator():
<some steps in the unit tests>
<raise XRPLModelException>
else:
<some more steps>
<raise TypeError>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for the resource. let me know if my usage looks nice.
xrpl/models/base_model.py
Outdated
@@ -73,7 +74,8 @@ def _value_to_json(value: XRPL_VALUE_TYPE) -> XRPL_VALUE_TYPE: | |||
return value | |||
|
|||
|
|||
@dataclass(frozen=True) | |||
# @require_kwargs_on_init | |||
@dataclass(frozen=True, **KW_ONLY_DATACLASS) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does BaseModel
need this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed it 👍
xrpl/models/utils.py
Outdated
# Code source for requiring kwargs: | ||
# https://gist.github.com/mikeholler/4be180627d3f8fceb55704b729464adb | ||
|
||
_T = TypeVar("_T") | ||
_Self = TypeVar("_Self") | ||
|
||
|
||
def is_kw_only_attr_defined_in_dataclass() -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a private method. It's fine to still use it in tests.
xrpl/models/utils.py
Outdated
|
||
# Unit tests that validate this behavior can be found at test_channel_authorize.py | ||
# and test_sign.py files. | ||
KW_ONLY_DATACLASS = dict(kw_only=True) if "kw_only" in dataclass.__kwdefaults__ else {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use the helper function is_kw_only_attr_defined_in_dataclass
.
xrpl/models/utils.py
Outdated
# When support for older versions of Python (earlier than v3.10) is removed, the | ||
# usage of require_kwargs_on_init decorator on model classes can also be removed. | ||
if is_kw_only_attr_defined_in_dataclass(): | ||
# noinspection PyTypeHints |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is this comment for? I don't think PyTypeHints is something this library uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay. I was trying to preserve the existing code. #noinspection PyTypeHints
is also used in the present code base. I've removed it in the latest tip of this branch 👍
Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
…unction definition
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm!
@mvadari could you take a look at this PR? do you suggest any other changes? |
@khancode @justinr1234 could you guys take a look at this PR at your convinience? |
Please let me know if there are any objections. I'd like to merge this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
use the kw_only=True parameter in the dataclass module. Ensure backwards compatibility for older versions of Python, this feature is enabled in Python v3.10 or later versions --------- Co-authored-by: Jackson Mills <jmills@ripple.com> Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
High Level Overview of Change
Introduces the use of
kw_only=True
parameter in thedataclass
module. It preserves backwards compatibility for older versions of Python, because this feature is enabled in Python v3.10 or later versionsContext of Change
The use of
@require_kwargs_on_init
decorator prevents IDE's from providing auto-complete suggestions. The newer versions of Python standard library allow developers to solve this issue usingkw_only=True
in the constructor ofdataclass
. Reference documentation: https://docs.python.org/3.10/library/dataclasses.html#module-contentsType of Change
Did you update CHANGELOG.md?
This change is based on the suggestion in this Stackoverflow page: https://stackoverflow.com/questions/72733998/kw-only-and-slots-dataclass-compatibility-with-older-versions-of-python
Thanks for the tip @mvadari !