Skip to content

Arganic

Arganic is a simple and lightweight Python library making it easy to manage Arguments for Classes, Methods or Functions.

The library provides a very simple and comprehensive set of decorators with advanced features such as required arguments, validators, type checking, read/write accesses, default values, and choices.

By leveraging Python's *args and **kwargs, Arganic empowers developers to enhance the readability and functionality of their codebase with ease.

Key Features:

  • Decorators for Classes, Methods, and Functions: Apply decorators to classes, methods, and functions to extend their functionality and behavior.
  • Required Parameters: Define required parameters for methods and functions to ensure essential inputs are provided.
  • Validator Support: Validate method arguments, function parameters, and class properties using built-in or custom validators.
  • Type Checking: Enforce type constraints and ensure type consistency with flexible type checking capabilities.
  • Read/Write Access Control: Define read-only properties for classes, methods, and functions as needed.
  • Default Values: Set default values for function arguments, method parameters, and class properties to streamline code logic.
  • Choice Selection: Specify a list of choices for method arguments and function parameters, restricting input values to predefined options.

Read the full documentation

Installation

Pip

Install Arganic via the pip command:

pip install arganic

Git clone

Clone the github repository:

git clone https://github.com/Kiwea/arganic

Usage

Decorators

Arganic provides 3 distinct types of decorators:

  • @class_properties : A decorator for class properties allowing you to define the data managed by the class during construction and then access these values within the class.
  • @method_arguments : A decorator for class methods allowing you to constrain the arguments provided during the call but also to find the correctly formatted values within the method.
  • @function_arguments : A decorator for functions allowing you to constrain the arguments provided during the call but also to find the correctly formatted values within the function.

Decorating Class and Method

Example of a class decorator and a method of the same class, they can be used independently or together.

When initializing the class or calling the method, the values provided will be validated according to the parameters provided in the decorator.

Info

It is important to note that if we wish to decorate a class, it must extend the base class ArgumentHandler, so it will implement the methods set(), get() and the property values allowing access to property values.

Within the method it is also possible to access the values of the arguments and thus benefit from the processing carried out, such for example as finding a default value if no one was provided from the call.

Info

It's also important to note that the methods, functions or classes that are decorated have the arguments *args and **kwargs declared in their signatures. Currently support for other arguments in the signature is not supported.

A simple Vehicle class decorated with three properties, and his decorated method drive() .
from arganic.arguments import (
    class_properties,
    method_arguments,
    Argument,
    ArgumentHandler
)


@class_properties(
    name=Argument(
        type=str,
    ),
    type=Argument(
        type=str,
        choices=('car', 'truck', 'bike'),
        default='car'
    ),
    description=Argument(
        type=str,
        required=False
    ),
)
class Vehicle(ArgumentHandler):
    """
    A class that manage Vehicles.
    """
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)

    def get_properties(self) -> dict:
        print(self.values) # Validated values
        return self.values

    @method_arguments(
        start=Argument(
            type=str
        ),
        destination=Argument(
            type=str
        )
    )
    def drive(self, *args, **kwargs) -> dict:
        """
        Drive the vehicle
        """
        print(self.drive.arguments.values) # Validated values
        return self.drive.arguments.values


car_1 = Vehicle(name="Red car")
car_1.drive(start='Geneva', destination='Paris')
bike_1 = Vehicle(name="Yellow bike", type="bike")
bike_1.drive(start='Lyon', destination='Geneva')
truck_1 = Vehicle(name="Blue truck", type="truck", description="Very heavy truck.")
truck_1.drive(start='Madrid', destination='Paris')

Decorating Class, Method and a function

Another example with also a simple function and some validating options.

Another full-featured example decorating Class, Method and Function.
from arganic.arguments import (
    class_properties,
    method_arguments,
    function_arguments,
    ArgumentHandler,
    Argument
)
from arganic.validators import (
    File,
    Dir,
    Email,
    Url,
    MaxLength,
    MinLength
)


@class_properties(
    int_prop=Argument(
        type=int,
        default=1,
        read_only=False
    ),
    is_required=Argument(),
    is_choices=Argument(
        type=str,
        choices=('a', 'b', 'c'),
        required=False
    ),
    is_dir=Argument(
        type=str,
        required=False,
        validator=Dir()
    ),
    is_file=Argument(
        type=str,
        required=False,
        validator=File()
    )
)
class DecoratedClass(ArgumentHandler):

    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        print(self.get('int_prop'))

    def get_int_prop(self) -> int:
        return self.get('int_prop')

    @method_arguments(
        first_arg=Argument(
            type=float
        ),
        second_arg=Argument(
            type=str,
            validator=(MinLength(2), MaxLength(4)),
            required=False
        ),
        email=Argument(
            type=str,
            validator=Email(),
            required=False
        ),
        url=Argument(
            type=str,
            validator=Url(),
            required=False
        )
    )
    def decorated_method(self, *args, **kwargs) -> float:
        return self.decorated_method.arguments.get('first_arg')


@function_arguments(
    arg_1=Argument(
        type=str
    ),
    arg_2=Argument(
        type=(int, float),
        required=False
    )
)
def decorated_function(*args, **kwargs) -> str:
    return decorated_function.arguments.get('arg_1')

Arguments parameters

Another example of a class decorated with a full-featured property.

List of the available parameters the Argument can take:

  • default: (Any, optional) - The default value the argument will take if no value is provided.
  • read_only: (bool, Default=True) - The argument is read-only, so it can no longer be modified.
  • required: (bool, default=True) – The argument is required if the value is missing: an Exception will occur.
  • type: (Type | tuple[Type], optional) – Defines the type(s) of values accepted for this argument, if the type provided is not valid an Exception will occur.
  • validator: (Validator | tuple[Validator], optional) – One or more instances of validators constraining the value that an argument can have.
  • choices: (tuple, optional) – A list of choices limiting the values that the supplied arguments can have.
Full featured argument..
from arganic.arguments import Argument, class_properties, ArgumentHandler  # Import Argument class.
from arganic.validators import MinLength, MaxLength  # Import validators


# Argument example
@class_properties(
    arg1=Argument(
        type=(str, list, tuple),  # Multiple types are supported.
        required=False,  # This argument is not required.
        default='first',  # A default value.
        read_only=False,  # This Argument/property is not writeable.
        choices=('first', 'second', ['first', 'second'], ('first', 'second')),  # Available choices.
        validator=(MinLength(1), MaxLength(10))  # Validators.
    )
)
class FullFeaturedArgument(ArgumentHandler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def get_arg1(self) -> str | list | tuple:
        return self.get('arg1')


# 'first' value is allowed in choices options
ffa1 = FullFeaturedArgument(arg1='first')
print(ffa1.get_arg1())

# arg1 is not required and will take de the default value 'first'
ffa2 = FullFeaturedArgument()
print(ffa2.get_arg1())

# arg1 can be a tuple
ffa3 = FullFeaturedArgument(arg1=('first', 'second'))
print(ffa3.get_arg1())

# arg1 can be a list
ffa4 = FullFeaturedArgument(arg1=['first', 'second'])
print(ffa4.get_arg1())

# arg1 can be set
ffa4 = FullFeaturedArgument(arg1=['first', 'second'])
print(ffa4.get_arg1())
ffa4.set('arg1', 'second')
print(ffa4.get_arg1())

Custom validators

It's possible to define your own validators by extending the Validator class.

A custom validator example.
from arganic.arguments import function_arguments, Argument
from arganic.validators import Validator


class CityValidator(Validator):
    """
    Custom validator class.
    """
    def validate(self, value) -> bool:
        if value in ('Geneva', 'Paris', 'Lyon', 'Madrid'):
            return True
        raise ValueError('Invalid value')


@function_arguments(
    start=Argument(
        type=str,
        validator=CityValidator()
    ),
    destination=Argument(
        type=str,
        validator=CityValidator()
    )
)
def drive(*args, **kwargs) -> None:
    print('Drive')
    print('start', drive.arguments.get('start'))
    print('destination', drive.arguments.get('destination'))
    return drive.arguments.values


drive(start='Geneva', destination='Paris')
drive(start='Lyon', destination='Geneva')
drive(start='Madrid', destination='Paris')

Contributing

This project is open and gratefully accepts any form of contribution.

Contributing to the code

Create a virtual env:

cd Arganic
python -m venv venv
source venv/bin/activate

Test the code:

pytest --cov=arganic/

Create a feature branch:

git checkout -b my-feature

Add your code and test it again Update the documentation under the docs/

mkdocs serve

Submit a push request...

Issues

If you find a bug, please post an issue on the issue tracker on GitHub.

To help reproduce the bug, please provide a minimal reproducible example, including a code snippet and the full error message.