Paramater Types
arc uses Python type hints for data conversion / validation.
When Possible, arc uses builtin and standard library data types for arguments. But if no type is available, or the builtin types don't provide the neccessary functionality, arc may implement a custom type.
Standard Libary Types¶
str
This is considered the default type is no type is specified. str(v)
is used which, in most cases, will be comparable to no change
int
arc uses int(v)
to convert the value. Note that decimal input (1.4
) will result in an error, not a narrowing operation.
float
Likewise, arc uses float(v)
. Ingeter values will be converted to a float (2 -> 2.0
)
bool
Used to denote a Flag
bytes
Converted using v.encode()
Collection Types¶
list
Collection types (list
, set
, tuple
) can be used to gather multiple values into a single parameter.
When used as a positional parameter list
acts similarly to *args
import arc
@arc.command()
def main(names: list = []):
if not names:
print("No names :(")
else:
for name in names:
print(name)
main()
$ python list_argument.py
No names :(
$ python list_argument.py Jonathen Joseph Jotaro
Jonathen
Joseph
Jotaro
list
can accept any number of values, you won't be able to add additional arguments after names
. Any other positional arguments would have to come before names
.
When used as an option, it allows the option to be used multiple times:
import arc
@arc.command()
def main(*, names: list = []):
if not names:
print("No names :(")
else:
for name in names:
print(name)
main()
$ python list_option.py
No names :(
$ python list_option.py --names Josuke --names Giorno --names Joylene
Josuke
Giorno
Joylene
import arc
@arc.command()
def main(nums: list[int] = []):
if not nums:
print("Provide some integers to sum up")
else:
total = 0
for val in nums:
total += val
print("The total is: ", total)
main()
set
Similar to list
, but will filter out any non-unique elements.
import arc
@arc.command()
def main(vals: set):
print("Unique values:")
print("\n".join(vals))
main()
tuple
Similar to list
, but with some additional functionality.
According to PEP 484:
tuple
represents an arbitrarily sized tuple of any type. In arc, this will behave the same aslist
tuple[int, ...]
represents an arbitrarily sized tuple of integers. In arc, this will behave the same aslist[int]
tuple[int, int]
represents a size-two tuple of integers. In arc, this behavior is unique totuple
as the parameter will only select 2 values from input.
dict
Allows a list of comma-seperated key-value pairs. Can be typed generically on both keys and values.
import arc
@arc.command()
def command(numbers: dict[str, int]):
print(numbers)
command()
typing.Union
Allows the input to be multiple different types.
from typing import Union
import arc
@arc.command()
def main(number: Union[int, str]):
print(type(number))
main()
arc will attempt to coerce the input into each type, from left to right. The first to succeed will be passed along to the command.
Warning
Currently arc's behavior with collections in union types is not defined. As such, it is not reccomended that you give an argument a type similar to typing.Union[list, ...]
typing.Literal
Enforces that the input must be a specific sub-set of values
from typing import Literal
import arc
@arc.command()
def main(word: Literal["foo", "bar", 1]):
print(word, type(word))
main()
$ python literal_argument.py foo
foo <class 'str'>
$ python literal_argument.py 1
1 <class 'int'>
$ python literal_argument.py other
Invalid value for word: must be foo, bar or 1
Note
arc compares the input to the string-ified version of each value in the Literal. So for the second example above, the comparison that succedded was "1" == "1"
not 1 == 1
.
typing.TypedDict
Constrains a dictionary input to a specific subset of keys and specific value types.
typing.Optional
Indicates an optional parameter with a default of None
. The following are functionality equivalent
from typing import Optional
from arc import CLI
cli = CLI()
@cli.command()
def c1(val: Optional[int]):
print(val)
@cli.command()
def c2(val: int = None):
print(val)
cli()
$ python optional_argument.py c1
None
$ python optional_argument.py c1 1
1
$ python optional_argument.py c2
None
$ python optional_argument.py c2 2
2
pathlib.Path
Path won't perform any validation checks to assert that the input is a valid path, but it just offers the convenience of working with path objects rather than strings. Check the ValidPath
custom type for additional validations
enum.Enum
Similar to typing.Literal
, restricts the input to a specific sub-set of values
from enum import Enum
import arc
from arc.params import Param
class Color(Enum):
RED = "red"
YELLOW = "yellow"
GREEN = "green"
@arc.command()
def paint(color: Color = Param(prompt="What color do you want to paint?")):
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 greens")
paint()
$ python paint.py red
You painted the walls the bloodiest of reds
$ python paint.py blue
Invalid value for color: must be red, yellow or green
ipaddress.IPv4Address
Uses ipaddress.IPv4Address(v)
for conversion, so anything valid there is valid here.
ipaddress.IPv6Address
Same as above
Arc Types¶
arc provides a variety of additional types exported from the arc.types
module:
Warning
Most custom arc types are usable as a valid arc parameter type and as a typical Python class. For example, you can create a SemVar
object easily like so: SemVer.parse('1.2.3')
. In some particular instances, a type can only be used as a argument type and not as a valid constructor. These types will be denoted with 🛇
following the type name.
Context
Gives you access to the current execution context instance which serves as a central data and functionality object
State
Reference State for details
Range
TODO
SemVer
TODO
User
(UNIX ONLY)
A representation of a unix user.
Group
(UNIX ONLY)
A representation of a unix group.
File System Types¶
File
TODO
ValidPath
Subclass of pathlib.Path
but asserts that the provided path actually exists
FilePath
Subclass of ValidPath
but asserts that the path both exists and is a file
DirectoryPath
Subclass of ValidPath
but assets that the path both exists and is a directory
Networking Types¶
IpAddress
🛇
Can be either IPv4Address
or IPv6Address
. Equivelant to typing.Union[IPv4Address, IPv6Address]
Url
Parses the strings input using urllib.parse.urlparse
HttpUrl
Url
that asserts the scheme to be http
or https
PostgresUrl
Url
that asserts the scheme to be postgresql
or postgres
Number Types¶
Note
For any types that simply change the base of the input (like Binary
or Hex
), it is essentially equivelant to int(v, base=<base>)
.
Binary
Accepts integers as binary stings (0101010110
).
Oct
Accepts integers in base 8
Hex
Accepts integers in base 16
PositveInt
Enforces that the integer must be greater than 0
NegativeInt
Enforces that the integer must be less than 0
PositveFloat
Enforces that the float must be greater than 0
NegativeFloat
Enforces that the float must be less than 0
AnyNumber
🛇
Accepts floats, and integers in any base.
String Types¶
Char
Enforces that the string can only be a single character long
Strict Types¶
strictstr
strictint
strictfloat
stricturl
strictpath
Custom Types¶
When implementing your own types, you can make them compatible with arc by implementing the __convert__()
class method. Arc will call this with the input from the command line, and you are expected to parse the input and return an instance of the type.
import arc
class CustomType:
def __init__(self, val: int):
self.val = val
def __str__(self):
return f"CustomType(val={self.val})"
@classmethod
def __convert__(cls, value: str):
if value.isnumeric():
return cls(int(value))
else:
raise arc.ConversionError(value, "must be an integer")
@arc.command()
def main(foo: CustomType):
print(foo)
main()
$ python custom_type.py 2
CustomType(val=2)
$ python custom_type.py string
Invalid value for foo: must be an integer
Some notes about custom types:
- In additon to
value
, you can also add the following arguments to the signature (in the given order, but the names don't need to match): info
: Description of the provided type. Instance ofarc.types.TypeInfo
ctx
: The current execution context. Instance ofarc.context.Context
Tip
Any type that implements __enter__()
and __exit__()
, will be treated like a context manager. The manager will be opened before the command executes, and closed afterwards.
Type Aliases¶
Type aliases are the way in which arc implements support for builtin and standard library Python types, but can also be used for any type. You can use type aliases to provide support fo any third party library types. For example, supporting numpy arrays
import arc
from arc.types import Alias
import numpy as np
# Inherits from Alias. The 'of' parameter declares what types(s) this
# alias handles (can be a single type or tuple of types).
# Reads like a sentence: "NDArrayAlias is the Alias of np.ndarray"
class NDArrayAlias(Alias, of=np.ndarry):
@classmethod
def __convert__(cls, value: str):
return np.ndarray(value.split(","))
@arc.command()
def main(array: np.ndarray):
print(repr(array))
main()
Note that this is a simplified example, a more complete implementation would support the use of generics using numpy.typing
Generic Types¶
TODO