SimpleParsing icon indicating copy to clipboard operation
SimpleParsing copied to clipboard

decoding_fn never gets called

Open mezuzza opened this issue 1 year ago • 2 comments

Describe the bug decoding_fns never trigger

To Reproduce

#!/usr/bin/env python3
from functools import wraps
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import simple_parsing

from dataclasses import dataclass
from collections.abc import Callable

from simple_parsing.helpers.serialization import register_decoding_fn

class Secret[T]:
    def __init__(self, value: T) -> None:
        self.value = value

    def __repr__(self) -> str:
        return rf"Secret(value={self.value!r})"

def parse_secret[T](parse: Callable[[str], T]) -> Callable[[str], Secret[T]]:
    @wraps(parse)
    def wrapper(s: str):
        print("Parsing secret")
        return Secret(parse(s))

    return wrapper


def parse_ed25519(s: str) -> Ed25519PrivateKey:
    print("Parsing ed25519")
    key = load_pem_private_key(s.encode(), None)
    match key:
        case Ed25519PrivateKey():
            return key
        case _:
            raise ValueError("Provided PEM key is not ed25519")

register_decoding_fn(Secret, parse_secret(parse_ed25519))

@dataclass(slots=True)
class Config:
    # ed25519 private key used to sign api keys
    key: Secret[Ed25519PrivateKey] | None = simple_parsing.field(
        default=None,
        decoding_fn=parse_secret(parse_ed25519),
    )

print(simple_parsing.parse(Config, args=["--key", "test"]))

Expected behavior The arg should be passed to parse_secret and raise an exception.

Actual behavior The key is parsing without any issues.

» ./test.py
Config(key=Secret(value='test'))

Desktop (please complete the following information):

  • Version 0.1.5
  • Python version: 3.12.2

mezuzza avatar May 05 '24 23:05 mezuzza

Fully acknowledge that I may have a total misunderstanding of the intent, but I did find a workaround. Given that this is totally undocumented and relies on some particular implementation details, I'm guessing this isn't the intended solution.

#!/usr/bin/env python3
from functools import wraps
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import simple_parsing

from dataclasses import dataclass
from collections.abc import Callable

from simple_parsing.helpers.serialization import register_decoding_fn

class Secret[T]:
    def __init__(self, value: T) -> None:
        self.value = value

    def __repr__(self) -> str:
        return rf"Secret(value={self.value!r})"

def parse_secret[T](parse: Callable[[str], T]) -> Callable[[str], Secret[T]]:
    @wraps(parse)
    def wrapper(s: str):
        print("Parsing secret")
        return Secret(parse(s))

    return wrapper


def parse_ed25519(s: str) -> Ed25519PrivateKey:
    print("Parsing ed25519")
    key = load_pem_private_key(s.encode(), None)
    match key:
        case Ed25519PrivateKey():
            return key
        case _:
            raise ValueError("Provided PEM key is not ed25519")

register_decoding_fn(Secret, parse_secret(parse_ed25519))

@dataclass(slots=True)
class Config:
    # ed25519 private key used to sign api keys
    key: Secret[Ed25519PrivateKey] | None = simple_parsing.field(
        default=None,
        metadata={"custom_args": {"type": parse_secret(parse_ed25519)}},
    )

print(simple_parsing.parse(Config, args=["--key", "test"]))

mezuzza avatar May 06 '24 05:05 mezuzza

Hey there, the decoding_fn is called when serializing / deserializing a dataclass, whereas the type arg of argparse (which you're using here) is used when parsing the field / dataclass from the command-line.

Sorry if this isn't clear from the code / very limited docstrings.

lebrice avatar May 24 '24 15:05 lebrice