Skip to content

add_inputs, add_outputs & apply method to facilitate method chaining #124

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

Closed
wants to merge 2 commits into from

Conversation

Et9797
Copy link

@Et9797 Et9797 commented Nov 5, 2022

Motivation

To facilitate method chaining syntax when building transactions. Without apply it is less convenient to apply certain logic to a tx.
The proposed .apply method takes a callback function to apply to the transaction.

A minimal working example

from pycardano import (
  BlockFrostChainContext,
  Network,
  PaymentSigningKey,
  PaymentVerificationKey,
  Address,
  TransactionBuilder,
  Transaction,
  TransactionOutput
)

chain_context = BlockFrostChainContext(
  project_id = "preprodAVsU2w89ixOk15AIOsq4pgzEGKBrv7Rd",
  network = Network.TESTNET,
  base_url = "https://cardano-preprod.blockfrost.io/api/"
)

usr1_skey = PaymentSigningKey.load(f"keys/usr1.skey")
usr1_vkey = PaymentVerificationKey.from_signing_key(usr1_skey)
usr1_addr = Address(usr1_vkey.hash(), network=Network.TESTNET)

usr2_skey = PaymentSigningKey.load(f"keys/usr2.skey")
usr2_vkey = PaymentVerificationKey.from_signing_key(usr2_skey)
usr2_addr = Address(usr2_vkey.hash(), network=Network.TESTNET)


class CustomTxBuilder:

  def add_utxos_with_ten_ada(self, builder):
    # a callback function with arbitrary logic to apply to the tx.
    # in this example a builder parameter must be added to accept builder1/builder2.
    # the callback shouldn't return anything (None), as nothing will be done
    # with the return value.
    for utxo in chain_context.utxos(str(usr1_addr)):
      if utxo.output.amount == 10_000_000:
        builder.add_input(utxo)


  def construct_tx1(self) -> Transaction:
    builder1 = TransactionBuilder(chain_context)
    (
    builder1
      # .add_input(chain_context.utxos(str(usr1_addr))) # list of UTxOs
      .apply(lambda: self.add_utxos_with_ten_ada(builder1))
      .add_output(
        TransactionOutput(
          address = usr2_addr,
          amount = 20_000_000
        )
      ) 
      .add_output(
        TransactionOutput(
          address = usr2_addr,
          amount = 20_000_000
        )
      ) 
    )
    return builder1.build_and_sign(signing_keys=[usr1_skey], change_address=usr1_addr)

  def construct_tx2(self) -> Transaction:
    builder2 = TransactionBuilder(chain_context)
    # etc.


custom_builder = CustomTxBuilder()
signed_tx = custom_builder.construct_tx1()
print(signed_tx.transaction_body.inputs)
chain_context.submit_tx(signed_tx.to_cbor())
print("Tx submitted.")

@daehan-koreapool
Copy link
Contributor

Is this still a WIP merge request?

For updating .add_input() method, I think keeping it as it used to be is more explicit.
Rather than introducing a new ambiguity with conditionals, supporting another dedicated method like .add_inputs() for handling multiple inputs seems better in my opinion.

Regarding the new .apply() method, I would like to hear @cffls 's opinion to see if we really need this feature.
Method chaining doesn't always need to end with build_and_sign, so I am personally OK with passing an TransactionBuilder instance to another function for applying arbitrary logic without the new .apply() syntax.

@Et9797
Copy link
Author

Et9797 commented Nov 5, 2022

Is this still a WIP merge request?

For updating .add_input() method, I think keeping it as it used to be is more explicit. Rather than introducing a new ambiguity with conditionals, supporting another dedicated method like .add_inputs() for handling multiple inputs seems better in my opinion.

Regarding the new .apply() method, I would like to hear @cffls 's opinion to see if we really need this feature. Method chaining doesn't always need to end with build_and_sign, so I am personally OK with passing an TransactionBuilder instance to another function for applying arbitrary logic without the new .apply() syntax.

Lucid's collectFrom does it oppositely and expects a list of Utxos. I think it would be nicer that you have the option to do both so it's more flexible. Regardless a list of Utxos would definitely be better, because rarely do you ever select just a single UTxO.
Regarding your second point, in my opinion having the txbuilder object in one place, and sequentially adding to it with method chaining syntax makes more sense intuitively, than just passing it around to other methods. With the former you can from a bird's eye view quickly see the full tx structure, however with the latter you have to traverse all the function logic. Quickly becomes a pain to maintain.

@codecov-commenter
Copy link

codecov-commenter commented Nov 5, 2022

Codecov Report

Merging #124 (46ca732) into main (a2c3a8e) will decrease coverage by 0.98%.
The diff coverage is 31.25%.

@@            Coverage Diff             @@
##             main     #124      +/-   ##
==========================================
- Coverage   86.36%   85.38%   -0.99%     
==========================================
  Files          24       24              
  Lines        2647     2661      +14     
  Branches      514      518       +4     
==========================================
- Hits         2286     2272      -14     
- Misses        267      295      +28     
  Partials       94       94              
Impacted Files Coverage Δ
pycardano/txbuilder.py 89.72% <31.25%> (-1.93%) ⬇️
pycardano/backend/ogmios.py 54.10% <0.00%> (-4.85%) ⬇️
pycardano/address.py 76.40% <0.00%> (-1.26%) ⬇️
pycardano/serialization.py 87.44% <0.00%> (-0.23%) ⬇️
pycardano/plutus.py 91.23% <0.00%> (-0.14%) ⬇️
pycardano/key.py 86.71% <0.00%> (ø)
pycardano/transaction.py 94.25% <0.00%> (+0.01%) ⬆️

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

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.

Agreed with @daehan-koreapool that creating a new function add_inputs is probably better.

I am fine with adding this apply function, since it is required for chaining calls (although I am not a big fan of it 😅).

Comment on lines 172 to 173
for o in utxo:
self.inputs.append(o)
Copy link
Collaborator

Choose a reason for hiding this comment

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

self.inputs.extend(utxo) is simpler.

Copy link
Author

@Et9797 Et9797 Nov 5, 2022

Choose a reason for hiding this comment

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

.

If add_inputs is added, then it probably makes sense as well to add an .add_outputs method. So now you're introducing two methods.. which will make the auto-completion list grow in editors😄
One argument for having two separate functions though would be that for new devs the Union syntax could be a bit confusing to read. Hence why I like the new alternative | union type hint introduced in 3.10: UTxO | List[UTxO]
So what I can think of for now, the pros and cons for separate methods:

  • Unambiguous
  • Probably clearer for new devs

Cons:

  • Will need an .add_outputs too, so more auto-completion options in editors
  • More code

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am fine with either way, will let you decide :D For union type, we will probably still need to use Union, because the library needs to support not only 3.10+, but anything above 3.7.

Copy link
Author

Choose a reason for hiding this comment

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

I don't mind haha - maybe do an emoji poll on Discord to see what most people would prefer

Copy link
Contributor

Choose a reason for hiding this comment

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

Since we need to support Python3.7 and up, we should stick to Union type hint for the time being throughout the entire code base.

Mypy is set to target Python3.7 for type static analysis as well.


Returns:
TransactionBuilder: Current transaction builder.
"""
self.inputs.append(utxo)
if callable(callback):
callback()
Copy link
Collaborator

Choose a reason for hiding this comment

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

A simpler approach is make the type of this callable to take the builder as the input, like this:

def apply(self, callback: Callable[[TransactionBuilder], None]) -> TransactionBuilder:
    if callable(callback):
        callback(self)
    else:
        raise ValueError("Not a callable.")
    return self

Doing so, we don't need to create a lambda function in caller's code:

def add_utxos_with_ten_ada(builder):
  for utxo in chain_context.utxos(str(usr1_addr)):
    if utxo.output.amount == 10_000_000:
      builder.add_input(utxo)

class CustomTxBuilder:

  def construct_tx1(self) -> Transaction:
     builder1 = TransactionBuilder(chain_context)
    (
    builder1
      # .add_input(chain_context.utxos(str(usr1_addr))) # list of UTxOs
      .apply(add_utxos_with_ten_ada)
      .add_output(
        TransactionOutput(
          address = usr2_addr,
          amount = 20_000_000
        )
      ) 
      .add_output(
        TransactionOutput(
          address = usr2_addr,
          amount = 20_000_000
        )
      ) 
    )

Copy link
Author

Choose a reason for hiding this comment

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

Good point. Only drawback is that you're now required to have a builder parameter in the callback every time. And when the callback is going to take more arguments than just the builder, you'll end up using a lambda again.

def add_utxos_with_ten_ada(builder, p1, p2):
    ....

Now the lambda will need to take a builder arg too, like the following:
apply(lambda builder: add_utxos_with_ten_ada(builder, arg1, arg2)
As opposed to:
apply(lambda: add_utxos_with_ten_ada(builder1, arg1, arg2)

I think the number of times you'll have to apply additional params is a handful of cases though, so in that case it's easier with your suggestion because it avoids the lambda altogether. On the other hand lambdas can be a bit confusing and having to provide no input is also convenient

Copy link
Collaborator

Choose a reason for hiding this comment

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

This problem could be solved by using (*args, **kwargs) like this:

def apply(self, callback: Callable[[TransactionBuilder, ], None], *args, **kwargs) -> TransactionBuilder:
    if callable(callback):
        callback(self, *args, **kwargs)
    else:
        raise ValueError("Not a callable.")
    return self
def add_utxos_with_ten_ada_with_params(builder, to_print, my_keyword=None):
  for utxo in chain_context.utxos(str(usr1_addr)):
    if utxo.output.amount == 10_000_000:
      builder.add_input(utxo)
  print(to_print)
  print(my_keyword)

class CustomTxBuilder:

  def construct_tx1(self) -> Transaction:
     builder1 = TransactionBuilder(chain_context)
    (
    builder1
      # .add_input(chain_context.utxos(str(usr1_addr))) # list of UTxOs
      .apply(add_utxos_with_ten_ada, "my_positional_arg", my_keyword="my_keyword_arg")
      .add_output(
        TransactionOutput(
          address = usr2_addr,
          amount = 20_000_000
        )
      ) 
      .add_output(
        TransactionOutput(
          address = usr2_addr,
          amount = 20_000_000
        )
      ) 
    )

Copy link
Author

Choose a reason for hiding this comment

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

I thought of this as well and tried it, but the problem is that it gives a type error.

image

We need to add *args and **kwargs to the type signature of callback. I tried looking up if there's a canonical way of doing this, but there doesn't seem to be until 3.10. Assuming Any type does work:
def apply(self, callback: Callable[[TransactionBuilder, Any, Any], None], *args, **kwargs) -> TransactionBuilder:
Not the prettiest solution😄

Copy link
Author

@Et9797 Et9797 Nov 6, 2022

Choose a reason for hiding this comment

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

Actually, that gives a type error again when you remove the optional args from add_utxos_with_ten_ada

Copy link
Author

@Et9797 Et9797 Nov 6, 2022

Choose a reason for hiding this comment

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

Only solution to make it type correct so far seems to be callback: Callable[..., None].
https://stackoverflow.com/a/68472403/12541069
However, this also means TransactionBuilder is an optional arg, which it shouldn't be. Therefore I propose for now to just restrict it to TransactionBuilder as only arg, and allow no other added optional arguments in the callback:

def apply(self, callback: Callable[[TransactionBuilder], None]) -> TransactionBuilder:
    if callable(callback):
        callback(self)
    else:
        raise ValueError("Not a callable.")
    return self

So basically your first suggestion I think is best. A user can still add parameters if they really need to. They'll have to add a lambda in that case. But in most cases it'll just be the direct callback function without a lambda.
apply(lambda builder: add_utxos_with_ten_ada(builder, arg1, arg2)

Copy link
Collaborator

@cffls cffls Nov 6, 2022

Choose a reason for hiding this comment

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

Thanks for experimenting these options. I don't think it is worth enforcing strict typing in this use case, because apply is supposed to be very generic, and we are kind of creating a problem for ourselves here. I prefer using callback(self, *args, **kwargs) and put a @typing.no_type_check decorator for apply to bypass mypy type checking. This will make everything concise again.

Copy link
Author

@Et9797 Et9797 Nov 6, 2022

Choose a reason for hiding this comment

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

Alright, makes sense. Avoids use of lambdas altogether

@daehan-koreapool
Copy link
Contributor

daehan-koreapool commented Nov 6, 2022

I do agree that supporting a list of UTxOs is better since multiple UTxOs will be used in most of the realistic cases.

In my opinion, we could introduce new .add_inputs() and .add_outputs() now, and add deprecation warning and timeline for removing .add_input() and .add_output() completely in the future so that only list inputs are allowed.

Syntactically, the users providing a single itemed UTxO with .add_inputs() isn't much different from providing a single UTxO by using .add_intput().

ex) https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
I think it's a good time for PyCardano to keep track of deprecation timeline and a clear direction of API changes to the user by adding deprecation warnings.

ex) https://docs.djangoproject.com/en/dev/internals/deprecation/
As an example, Django outlines all the future deprecation timelines ahead of the time so the users can prepare for any breaking changes in the future.

@cffls
Copy link
Collaborator

cffls commented Nov 6, 2022

I do agree that supporting a list of UTxOs is better since multiple UTxOs will be used in most of the realistic cases.

In my opinion, we could introduce new .add_inputs() and .add_outputs() now, and add deprecation warning and timeline for removing .add_input() and .add_output() completely in the future so that only list inputs are allowed.

Syntactically, the users providing a single itemed UTxO with .add_inputs() isn't much different from providing a single UTxO by using .add_intput().

ex) https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically I think it's a good time for PyCardano to keep track of deprecation timeline and a clear direction of API changes to the user by adding deprecation warnings.

ex) https://docs.djangoproject.com/en/dev/internals/deprecation/ As an example, Django outlines all the future deprecation timelines ahead of the time so the users can prepare for any breaking changes in the future.

I like this idea. However, I would like to see add_inputs supports a single UTxO as the input, because it feels verbose to wrap a UTxO inside a list bracket.

@Et9797
Copy link
Author

Et9797 commented Nov 6, 2022

I do agree that supporting a list of UTxOs is better since multiple UTxOs will be used in most of the realistic cases.
In my opinion, we could introduce new .add_inputs() and .add_outputs() now, and add deprecation warning and timeline for removing .add_input() and .add_output() completely in the future so that only list inputs are allowed.
Syntactically, the users providing a single itemed UTxO with .add_inputs() isn't much different from providing a single UTxO by using .add_intput().
ex) https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically I think it's a good time for PyCardano to keep track of deprecation timeline and a clear direction of API changes to the user by adding deprecation warnings.
ex) https://docs.djangoproject.com/en/dev/internals/deprecation/ As an example, Django outlines all the future deprecation timelines ahead of the time so the users can prepare for any breaking changes in the future.

I like this idea. However, I would like to see add_inputs supports a single UTxO as the input, because it feels verbose to wrap a UTxO inside a list bracket.

I agree with this. With regards to Union vs. |, as long as __future__ is imported the | symbol does work for union type hints in 3.7+. I personally like it more because it reads very naturally (reads like an 'or', rather than having to read the whole word 'Union'). Also saves some space. What do you guys prefer more?

@Et9797
Copy link
Author

Et9797 commented Nov 6, 2022

I do agree that supporting a list of UTxOs is better since multiple UTxOs will be used in most of the realistic cases.

In my opinion, we could introduce new .add_inputs() and .add_outputs() now, and add deprecation warning and timeline for removing .add_input() and .add_output() completely in the future so that only list inputs are allowed.

Syntactically, the users providing a single itemed UTxO with .add_inputs() isn't much different from providing a single UTxO by using .add_intput().

ex) https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically I think it's a good time for PyCardano to keep track of deprecation timeline and a clear direction of API changes to the user by adding deprecation warnings.

ex) https://docs.djangoproject.com/en/dev/internals/deprecation/ As an example, Django outlines all the future deprecation timelines ahead of the time so the users can prepare for any breaking changes in the future.

Perhaps the Deprecated package can aid with this https://pypi.org/project/Deprecated/
Introduces a dependency, but I guess it saves having a deprecated module in the pycardano codebase?

from deprecated import deprecated

@deprecated(version='1.2.1', reason="You should use another function")
def some_old_function(x, y):
    return x + y
hello.py:15: DeprecationWarning: Call to deprecated function (or staticmethod) some_old_function.
(You should use another function) -- Deprecated since version 1.2.0.
  some_old_function(12, 34)```

@cffls
Copy link
Collaborator

cffls commented Nov 6, 2022

I agree with this. With regards to Union vs. |, as long as future is imported the | symbol does work for union type hints in 3.7+. I personally like it more because it reads very naturally (reads like an 'or', rather than having to read the whole word 'Union'). Also saves some space. What do you guys prefer more?

Importing future and using "|" sounds fine to me.

@cffls
Copy link
Collaborator

cffls commented Nov 6, 2022

I do agree that supporting a list of UTxOs is better since multiple UTxOs will be used in most of the realistic cases.
In my opinion, we could introduce new .add_inputs() and .add_outputs() now, and add deprecation warning and timeline for removing .add_input() and .add_output() completely in the future so that only list inputs are allowed.
Syntactically, the users providing a single itemed UTxO with .add_inputs() isn't much different from providing a single UTxO by using .add_intput().
ex) https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically I think it's a good time for PyCardano to keep track of deprecation timeline and a clear direction of API changes to the user by adding deprecation warnings.
ex) https://docs.djangoproject.com/en/dev/internals/deprecation/ As an example, Django outlines all the future deprecation timelines ahead of the time so the users can prepare for any breaking changes in the future.

Perhaps the Deprecated package can aid with this https://pypi.org/project/Deprecated/ Introduces a dependency, but I guess it saves having a deprecated module in the pycardano codebase?

from deprecated import deprecated

@deprecated(version='1.2.1', reason="You should use another function")
def some_old_function(x, y):
    return x + y
hello.py:15: DeprecationWarning: Call to deprecated function (or staticmethod) some_old_function.
(You should use another function) -- Deprecated since version 1.2.0.
  some_old_function(12, 34)```

Yes, let's introduce this dependency. A side topic on this, currently there is no version info embedded in pycardano itself, I guess we need to introduce a version module to it, so users can easily tell the version from some call like this:

import pycardano

pycardano.__version__
"0.7.0"

@Et9797
Copy link
Author

Et9797 commented Nov 7, 2022

I agree with this. With regards to Union vs. |, as long as future is imported the | symbol does work for union type hints in 3.7+. I personally like it more because it reads very naturally (reads like an 'or', rather than having to read the whole word 'Union'). Also saves some space. What do you guys prefer more?

Importing future and using "|" sounds fine to me.

Doesn't work for type aliases though, so Union will have to remain for those.
python/mypy#9610 (comment)

@Et9797 Et9797 marked this pull request as draft November 7, 2022 03:30
@Et9797
Copy link
Author

Et9797 commented Nov 7, 2022

Maybe best to add them as two seperate methods for now as suggested by @daehan-koreapool, with no deprecations yet.

In the future when the number of TransactionBuilder methods increase it'll probably be wise to remove the single UTxO variants.
In the latest commit I added add_outputs which takes variable no. TransactionOutputs.

   def construct_tx1(self) -> Transaction:
    builder1 = TransactionBuilder(chain_context)
    (
    builder1
      .apply(self.add_utxos_with_ten_ada)
      .add_output(
        TransactionOutput(address = script_addr, amount = 5_000_000),
        datum = some_datum...,
        add_datum_to_witness = True
      )
      .add_outputs(
        TransactionOutput(address = usr2_addr, amount = 5_000_000),
        TransactionOutput(address = usr2_addr, amount = 5_000_000),
        TransactionOutput(address = usr2_addr, amount = 8_000_000)
      )
    )

add_output should still be used separately to add a datum to the witness set. I think it's better to have this distinction,
than implement the logic in add_outputs.

@Et9797 Et9797 changed the title List of UTxOs argument to add_input + apply method to facilitate method chaining add_inputs, add_outputs & apply method to facilitate method chaining Nov 7, 2022
@@ -310,6 +344,24 @@ def add_output(
self.datums[datum_hash(datum)] = datum
return self

def add_outputs(self, tx_out: TransactionOutput, *tx_outs) -> TransactionBuilder:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we have the parameter type as a List[TransactionOutput], similar to add_inputs?

Copy link
Contributor

Choose a reason for hiding this comment

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

I do agree List[TransactionOutput] is better even though it's pretty clever to allow users to provide multiple TransactionOutput through argument unpacking.

Copy link
Author

@Et9797 Et9797 Nov 8, 2022

Choose a reason for hiding this comment

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

Agree, but I wouldn't recommend not allowing varargs at all, because I think there's a strong case here for having them, as it saves typing out list brackets every time. The option to have List[TransactionOutput] + varargs as well as a single TransactionOutput + varargs would make it even more flexible syntactically imo.

In contrast, I don't think varargs syntax is justified for add_inputs, because most of the times you'll just be using filtered UTxOs retrieved from chain_context.utxos rather than writing your own UTxO objects yourself:

   def construct_tx1(self) -> Transaction:

    utxos: list[UTxO] = filter_func(chain_context.utxos(addr)) # more commonly

    builder1 = TransactionBuilder(chain_context)
    (
    builder1
      .add_inputs(
         UTxO(
            TransactionInput(tx_hash, id )
            TransactionOutput(addr, amount, ....)
         ), # rarely
         UTxO(
            TransactionInput(txhash, id),
            TransactionOutput(addr, amount, ....)
         ) # rarely
      )
    )

    (
    builder1
      .add_inputs(utxos) #more commonly
    )

Without varargs, extra brackets required:

   def construct_tx1(self) -> Transaction:
    builder1 = TransactionBuilder(chain_context)
    (
    builder1
      .add_outputs([
        TransactionOutput(address=usr2_addr, amount=5_000_000),
        TransactionOutput(address=usr2_addr, amount=5_000_000),
        TransactionOutput(address=usr2_addr, amount=8_000_000)
      ])
    )

Copy link
Collaborator

@cffls cffls Nov 8, 2022

Choose a reason for hiding this comment

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

I see your points. I was thinking more about the consistency in these APIs. What do you think of this:

def add_inputs(self, *utxos: UTxO]) -> TransactionBuilder

def add_outputs(self, *tx_outs: TransactionOutput) -> TransactionBuilder

This will make add_inputs and add_outputs more similar and less inconsistent, and users can pass both list and multi-args to them.

Outputs passed as a list:

my_output_list = get_output_list() # Get an output list from somewhere
builder1 = TransactionBuilder(chain_context)
    (
    builder1
      .add_outputs(*my_output_list)
    )

Outputs passed as multiple positional args:

builder1 = TransactionBuilder(chain_context)
    (
    builder1
      .add_outputs(
        TransactionOutput(address=usr2_addr, amount=5_000_000),
        TransactionOutput(address=usr2_addr, amount=5_000_000),
        TransactionOutput(address=usr2_addr, amount=8_000_000)
      )
    )

Copy link
Author

@Et9797 Et9797 Nov 8, 2022

Choose a reason for hiding this comment

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

Yes, I think this * version or the alternative List[...] only variant would then be the most succinct solutions. In the latter case then just stick with having to add brackets.
Only drawback with the * version is that it may seem that the * argument is optional, which it really isn't. Maybe good to have a check to see if it is empty and throw an error if it is. Not strictly required though because if it is empty it will just extend an empty tuple which doesn't throw a runtime error

Copy link
Collaborator

Choose a reason for hiding this comment

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

Cool, my preference is still using List[...], as it is more commonly used in Python.

Copy link
Contributor

Choose a reason for hiding this comment

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

Only drawback with the * version is that it may seem that the * argument is optional, which it really isn't. Maybe good to have a check to see if it is empty and throw an error if it is. Not strictly required though because if it is empty it will just extend an empty tuple which doesn't throw a runtime error

This concern is valid in my opinion.
When things are vague, explicit is better than implicit just like zen of python.
List[...] seems better for users since IDEs will display the expected type more clearly.

@cffls cffls added the enhancement New feature or request label Dec 7, 2022
@cffls
Copy link
Collaborator

cffls commented Jan 28, 2023

Hi @Et9797 , any update on this PR?

@Et9797
Copy link
Author

Et9797 commented Jan 29, 2023

Hi @Et9797 , any update on this PR?

Completely forgot about this! Been consumed with other stuff. I think the need for this PR is not high, therefore perhaps sideline it for now. If you want to add add_outputs/add_inputs you can of course. The problem with chaining is the bracket requirement in Python unlike in Javascript, which kind of makes it a visual annoyance and thing to think about. A library that I use frequently called plotnine, which ports R's ggplot requires similar syntax, which can be a bit annoying if you use it a lot.

@cffls
Copy link
Collaborator

cffls commented Jan 30, 2023

Thanks for the response. I will close this draft for now. Please feel free to reopen it if needed in the future.

@cffls cffls closed this Jan 30, 2023
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.

4 participants