Skip to content

Data Types

Sean Collings edited this page Jul 17, 2021 · 8 revisions

Data Types

Use

Typically, you don't want numbers, booleans, lists, represented as just strings. Arc provides type conversions that will convert the input to your desired type before passing it on to the command. If you've used Flask before, it's URL converters work in essentially the same way. If no type is specified, the input will be left as a string

Check examples/converters.py for full examples

Builtins

Arc supports type conversion to these builtin types:

  • int
  • float
  • bool
  • bytes
  • list
  • set
  • tuple

Examples

Int Conversion

from arc import CLI

cli = CLI()

@cli.subcommand()
def number_type(number: int):
    '''Prints the type of a number'''
    print(type(number))

cli()
$ python3 example.py number_type number=5
<class 'int'>

Float Converion

from arc import CLI
cli = CLI()

@cli.subcommand()
def float_type(number: float):
    '''Prints the type of a float'''
    print(type(number))

cli()
$ python3 example.py float_type number=5.3
<class 'float'>

List conversion

from arc import CLI
cli = CLI()

@cli.subcommand()
def list_type(numbers: list):
    '''Prints the type of a float'''
    print(type(number))
    print(numbers)

cli()
$ python3 example.py list_type numbers=1,2,3
<class 'list'>
["1", "2", "3"]

Collection Types can also be typed generically, to enable each element to be converted. Replace list with list[int] in the example above and then each item will be a integer, rather than a string. This works for set and tuple as well.

Bool conversion

Bool types doen't require a value and simple test for presence. By default, flags are considered False when not passed, and True when passed. This can be swapped by providing the arguments with the default value of True

from arc import CLI
cli = CLI()

@cli.subcommand()
def hello(name, reverse: bool):
    '''Command that prints greets someone'''
    if reverse:
        name = name[::-1]
    print(f"Hello, {name}!")

cli()
$ python3 example.py hello --name Sean
Hello, Sean!
$ python3 example.py hello --name Sean --reverse
Hello, naeS!

Note that flags are actually just syntatic suguar for arguments, so writing:

$ python3 example.py hello --name Sean --reverse true

Would have the same result

Standard Lib Types

Union Type

The union type is used to specify that an argument has two or more possible types.

from typing import Union
from arc import CLI
cli = CLI()

@cli.subcommand()
def union_type(number: Union[int, str]):
    '''Prints the type of a float'''
    print(type(number))

cli()
$ python3 example.py union_type number=5.3
<class 'int'>

$ python3 example.py union_type number=somestring
<class 'str'>

Arc will attempt to convert to each type within the union, from left to right, and the first one to succeed will be passed to the command. If none succeed, an error will be raised.

Path Type

The Path conversion doesn't perform any validation, but it just offers the convenience of working with path objects rather than strings.

from pathlib import Path
from arc import CLI

cli = CLI()

@cli.command()
def paint(path: Path):
    print(path.as_posix())

cli()

Enum Type

You can use the Python standard library Enum to define a specific subset of acceptable values to an argument.

from enum import Enum
from arc import CLI

cli = CLI()

class Color(Enum):
    RED = "red"
    YELLOW = "yellow"
    GREEN = "green"

@cli.command()
def paint(color: Color):
    if color == Color.RED:
        print("You painted the walls the bloodiest of reds")
    elif color == Color.YELLOW:
        print("You painted the walls the most fabulous yellow")
    else:
        print("You painted the walls the deepest of blues")

cli()
$ python3 example.py paint color=red
You painted the walls the bloodiest of reds

Since red is a defined value on the Color enumeration.

Literal Type

The literal type can also be used to define a limited subset of values that are acceptable.

from typing import Literal
from arc import CLI

cli = CLI()

@cli.command()
def pick_pain(color: Literal["red", "green", "blue", "yellow"]):
    print(f"You picked {color}")

cli()

Custom Types

File Type

A file type will take in a file path and return a file handler object

from arc import CLI
from arc.types import File
cli = CLI()

@cli.command()
def file_test(file=File[File.READ]):
    # file will be of custom type File, but has the
    # same interface as the TextIOWrapper returned by open()
    print(file.readlines())

The format for a File types is File[<FILE_MODE>] with the available file modes being:

  • READ
  • WRITE
  • APPEND
  • CREATE

Arc should handle the file cleanup process, however if you want to control when the file closes, you can with file.close(). Additionally, the file object implements __enter__ and __exit__ so can be used as a context manager.

from arc import CLI
from arc.types import File
cli = CLI()

@cli.command()
def file_test(file: File[File.READ]):
    print(file.readlines())
    file.close()

    # OR

    with file as f:
        print(f.readlines())

    # With either approach, file will defintely be closed at this point

# unless something goes horribly wrong, once the function finishes execution ARC will close the file for you though.

Range Type

The range type can assure that the user's input is within a specific value range

from typing import Literal
from arc import CLI
from arc.types import Range

cli = CLI()

@cli.command()
def range_test(value: Range[Literal[1], Litearl[10]]):
    print(value)
    print(type(value))
    print(val + 2)

The above example will only allows values between 1 and 10 (like the builtin this is inclusive on the minimum end and exclusive on the maximum end). While the type of value will be Range, Range is a subclass of int and so can be treated as such.

There are a couple of useful methods to note on the Range.

  • range - returns a builtin range() with the provided minimums and maximums
  • range_with_picked - Generator that returns a tuple pair of the value and whether or not that value was the one picked by the user.

ValidPath Type

Acts like a pathlib.Path object, but validates that the path exists first

from arc import CLI
from arc.types import ValidPath
cli = CLI()

@cli.command()
def paint(path: ValidPath):
    print(path.as_posix())

cli()

Input Data Type Converter Functions

Arc also allows you to use it's converter functionality when gathering user input from within a command

from arc import CLI
from arc.convert.input import convert_to_int

cli = CLI()

@cli.subcommand("example")
def example():
  cool_number = convert_to_int("Please enter a number: ")
  print(type(cool_number)) # '<class : int>'

cli()

Note that all convereters in Config.converter will have a function associated with it. This includes all custom converters. The functions will be named convert_to_<indicator>

Note that if the command defines a custom converter, it's respective input function will need to be imported after it is added to Config.converter otherwise it doesn't yet exist to import.

Clone this wiki locally