|
917 | 917 | } |
918 | 918 |
|
919 | 919 | If not provided, defaults to Cyclopts's internal coercion engine. |
920 | | - If a pydantic type-hint is provided, Cyclopts will disable it's internal coercion |
| 920 | + If a pydantic type-hint is provided, Cyclopts will disable its internal coercion |
921 | 921 | engine (including this `converter` argument) and leave the coercion to pydantic. |
922 | 922 |
|
| 923 | + The number of tokens passed to the converter is inferred from the type hint by default, |
| 924 | + but can be explicitly controlled with :attr:`~.Parameter.n_tokens`. This is useful when |
| 925 | + the type signature doesn't match the desired CLI token consumption. When loading complex |
| 926 | + objects with multiple fields, it may also be useful to combine with :attr:`~.Parameter.accepts_keys`. |
| 927 | + |
| 928 | + **Decorating Converters:** Converter functions can be decorated with :class:`.Parameter` to define |
| 929 | + reusable conversion behavior: |
| 930 | + |
| 931 | + .. code-block:: python |
| 932 | +
|
| 933 | + @Parameter(n_tokens=1, accepts_keys=False) |
| 934 | + def load_from_id(type_, tokens): |
| 935 | + """Load object from database by ID.""" |
| 936 | + return fetch_from_db(tokens[0].value) |
| 937 | +
|
| 938 | + @app.default |
| 939 | + def main(obj: Annotated[MyType, Parameter(converter=load_from_id)]): |
| 940 | + # Automatically inherits n_tokens=1 and accepts_keys=False |
| 941 | + pass |
| 942 | +
|
| 943 | + **Classmethod Support:** Converters can be classmethods. Use string references for class decoration |
| 944 | + or direct references in annotations. Classmethod signature should be ``(cls, tokens)`` instead of |
| 945 | + ``(type_, tokens)``: |
| 946 | + |
| 947 | + .. code-block:: python |
| 948 | +
|
| 949 | + @Parameter(converter="from_env") |
| 950 | + class Config: |
| 951 | + @Parameter(n_tokens=1, accepts_keys=False) |
| 952 | + @classmethod |
| 953 | + def from_env(cls, tokens): |
| 954 | + env = tokens[0].value |
| 955 | + configs = {"dev": ("localhost", 8080), "prod": ("api.example.com", 443)} |
| 956 | + return cls(*configs[env]) |
| 957 | +
|
| 958 | +
|
923 | 959 | .. attribute:: validator |
924 | 960 | :type: Union[None, Callable, Iterable[Callable]] |
925 | 961 | :value: None |
@@ -1225,6 +1261,9 @@ API |
1225 | 1261 | $ my-program --image foo.jpg nature |
1226 | 1262 | image=Image(path='foo.jpg', label='nature') |
1227 | 1263 |
|
| 1264 | + The ``accepts_keys=False`` option is commonly used with :attr:`~.Parameter.converter` and |
| 1265 | + :attr:`~.Parameter.n_tokens`. |
| 1266 | + |
1228 | 1267 | .. attribute:: consume_multiple |
1229 | 1268 | :type: Optional[bool] |
1230 | 1269 | :value: None |
@@ -1355,6 +1394,63 @@ API |
1355 | 1394 |
|
1356 | 1395 | See :ref:`Coercion Rules` for more details. |
1357 | 1396 |
|
| 1397 | + .. attribute:: n_tokens |
| 1398 | + :type: Optional[int] |
| 1399 | + :value: None |
| 1400 | + |
| 1401 | + Explicitly override the number of CLI tokens this parameter consumes. |
| 1402 | + |
| 1403 | + By default, Cyclopts infers the token count from the parameter's type hint |
| 1404 | + (e.g., :obj:`int` consumes 1 token, ``tuple[int, int]`` consumes 2, :obj:`list` consumes all remaining). |
| 1405 | + This attribute allows you to override that inference, which is particularly useful when: |
| 1406 | + |
| 1407 | + * Using custom converters that need a different token count than the type suggests. |
| 1408 | + * Loading complex types from a single token (e.g., loading from a file path). |
| 1409 | + * Implementing selection/lookup patterns where one token identifies an object. |
| 1410 | + |
| 1411 | + Values: |
| 1412 | + |
| 1413 | + * ``None`` (default): Infer token count from the type hint. |
| 1414 | + * non-negative integer: Consume exactly that many tokens. |
| 1415 | + * ``-1``: Consume all remaining tokens (similar to iterables). |
| 1416 | + |
| 1417 | + For ``*args`` parameters, ``n_tokens`` specifies tokens **per element**. |
| 1418 | + For example, ``n_tokens=2`` with 6 tokens creates 3 elements. |
| 1419 | + |
| 1420 | + .. code-block:: python |
| 1421 | +
|
| 1422 | + from cyclopts import App, Parameter |
| 1423 | + from typing import Annotated |
| 1424 | +
|
| 1425 | + class Config: |
| 1426 | + def __init__(self, host: str, port: int): |
| 1427 | + self.host = host |
| 1428 | + self.port = port |
| 1429 | +
|
| 1430 | + def load_config(type_, tokens): |
| 1431 | + # Load config from a file path (single token) |
| 1432 | + filepath = tokens[0].value |
| 1433 | + # ... load from file ... |
| 1434 | + return Config("example.com", 8080) |
| 1435 | +
|
| 1436 | + app = App() |
| 1437 | +
|
| 1438 | + @app.default |
| 1439 | + def main( |
| 1440 | + config: Annotated[ |
| 1441 | + Config, |
| 1442 | + Parameter(n_tokens=1, converter=load_config, accepts_keys=False) |
| 1443 | + ] |
| 1444 | + ): |
| 1445 | + print(f"Connecting to {config.host}:{config.port}") |
| 1446 | +
|
| 1447 | + app() |
| 1448 | +
|
| 1449 | + .. code-block:: console |
| 1450 | +
|
| 1451 | + $ my-script --config prod.conf |
| 1452 | + Connecting to example.com:8080 |
| 1453 | +
|
1358 | 1454 | .. automethod:: combine |
1359 | 1455 |
|
1360 | 1456 | .. automethod:: default |
|
0 commit comments