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

Typing for BaseConverter convert doesn't respect CONSUME_MULTIPLE_SEGMENTS #2396

Open
davetapley opened this issue Oct 30, 2024 · 6 comments
Labels
Milestone

Comments

@davetapley
Copy link
Contributor

Correct:

"""Convert a URI template field value to another format or type.
Args:
value (str or List[str]): Original string to convert.

Wrong, should be value: str | list[str]:

def convert(self, value: str) -> Any:

@davetapley
Copy link
Contributor Author

... although, str | list[str] is a bit awkward since the implementor will have to assert the correct type based on:

CONSUME_MULTIPLE_SEGMENTS: ClassVar[bool] = False

I'm guessing there's no way to express that relationship in typing.

If not I don't know how much appetite to e.g. make the current BaseConvertor private (i.e. _BaseConvertor) and expose a separate BaseConvertor with an explicit CONSUME_MULTIPLE_SEGMENTS, as with PathConverter.

@davetapley
Copy link
Contributor Author

davetapley commented Oct 30, 2024

... I thought I could workaround with:

Replace:

class MyConvertor(BaseConverter):
    CONSUME_MULTIPLE_SEGMENTS = True

With:

class MyConvertor(PathConverter):
    ...

... but PathConverter is typed to return str, not Any as BaseConverter 😑

def convert(self, value: Iterable[str]) -> str:

@jkmnt
Copy link

jkmnt commented Oct 31, 2024

I think the elegant (but breaking) API for such scalar-or-vector argument is vararg.

def convert(*value: str) -> Any

then convert('a') and convert ('a', 'b') are all valid.
It has the extra bonus of not needing to check isinstance(value, list) in implementation.

@vytas7
Copy link
Member

vytas7 commented Oct 31, 2024

In retrospect, even typing aside, I think this is a design mistake, I just didn't think of this specific detail before. The converter should always receive a string value, if needed, pre-joint on / by the router.

We cannot change this now, but we should plan a breaking change for 5.0.

@CaselIT
Copy link
Member

CaselIT commented Oct 31, 2024

You mean that the converter should pre-join them here?

class _CxSetFragmentFromRemainingPaths(_CxChild):
def __init__(self, segment_idx: int) -> None:
self._segment_idx = segment_idx
def src(self, indentation: int) -> str:
return '{0}fragment = path[{1}:]'.format(
_TAB_STR * indentation,
self._segment_idx,
)

so that the current path converter becomes a no-op?

Maybe I guess. Overall it would probably help if we were to require subclassing here, so we could avoid using that classvar.

@CaselIT CaselIT added the typing label Oct 31, 2024
@CaselIT CaselIT added this to the Version 4.x milestone Oct 31, 2024
@davetapley
Copy link
Contributor Author

Ooh, someone suggested a good workaround using Protocols here:

https://discuss.python.org/t/typing-instance-based-on-classvar/69716/2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants